Django + Celery + Apache mod_wsgi + Postgres + RabbitMQ 多客户端应用
Django + Celery + Apache mod_wsgi + Postgres + RabbitMQ Application for multiple clients
我有一个 Django 应用程序,它使用 Celery、RabbitMQ 和 Apache mod_wsgi。目前都在一台服务器上。每个客户端都有自己的 URL 挂载,例如:
每个客户都有自己的数据库和项目目录,其中 local_setting.py 用于他们的 Django 设置。
我正在使用 supervisord 为每个客户端管理 Celery Worker + Celery Beat。
随着我的客户越来越多,维护也越来越耗时。
我已经开始使用 Docker 来尝试简化部署,并可能在多个主机上进行扩展。
虽然为一个客户设置 Docker 组合 运行 一组服务非常容易,但我正在尝试为多个客户找出易于管理的最佳方法,例如快速设置安装在主 URL.
下的新客户端
我认为应该共享 Postgres 数据库实例以保存每个客户端数据库,就像现在一样。并拥有一个共享的 NGIX 实例来处理 HTTP 端。对于每个客户端,使用一个 Kubernetes Pod,包括:
- Gunicorn 来处理 Django
- 芹菜节拍
- 芹菜工人
- 用于静态文件的轻量级 HTTP 服务器。
所以问题是,这是一个好方法还是有更好的方法来处理和处理这个问题?
我还想知道我是否应该为每个客户构建一个图像,因为这样可能更容易管理?
欢迎任何建议。
处理客户的三种方式:
- 孤立的方法:独立的数据库。每个租户都有自己的数据库。
- 半隔离方法:共享数据库,独立模式。所有租户一个数据库,但每个租户一个模式。
- 共享方法:共享数据库、共享架构。所有租户共享相同的数据库和模式。有一个 main tenant-table,所有其他表都有一个指向的外键。
django-tenents 包使用第二种方法,并有一个 sub-domains 用于客户端,例如 client1.example.com、client2.example.com 等
我在第三次使用了 django-tenants,为我创建的每个模型添加了一个 Company
模型外键。
django 租户有帮助,但在 postgres 中有不同的架构;开销集成较少。
添加 Company
必须在每个模型中实现,如果您使用的是基于 class 的视图,则应使用中间件或混入处理。
我的建议是为此保留一个代码库和一台服务器 运行ning(或同一 Django 应用程序的多个服务器,无需任何基于客户端的定制)。主要原因是为了维护更容易。您不希望多次更改以向多个客户端提供功能。
由于您已经有一个 Django 应用程序,我认为最好利用该代码来适应上面给出的方法,同时对代码进行最少的更改。意思是,您需要某种方法来处理连接到多个数据库的多个客户端。我建议是使用中间件和 database router. Like this:(codes based on this snippet).
import threading
request_cfg = threading.local()
class RouterMiddleware (object):
def process_view( self, request, view_func, args, kwargs ):
if 'client' in kwargs:
request_cfg.client = kwargs['client']
request.client = client
# Here, we are adding client info with request object.
# It will make the implementation bit easier because
# then you can access client information anywhere in the view/template.
def process_response( self, request, response ):
if hasattr( request_cfg, 'client' ):
del request_cfg.client
return response
class DatabaseRouter (object):
def _default_db( self ):
from django.conf import settings
if hasattr( request_cfg, 'client' ) and request_cfg.client in settings.DATABASES:
return request_cfg.client
else:
return None
def db_for_read( self, model, **hints ):
return self._default_db()
def db_for_write( self, model, **hints ):
return self._default_db()
然后将它们添加到 settings.py
:
DATABASES = {
'default': {
'NAME': 'user',
'ENGINE': 'django.db.backends.postgresql',
'USER': 'postgres_user',
'PASSWORD': 's3krit'
},
'client1': {
'NAME': 'client1',
'ENGINE': 'django.db.backends.postgresql',
'USER': 'postgres_user',
'PASSWORD': 's3krit'
},
'client2': {
'NAME': 'client2',
'ENGINE': 'django.db.backends.postgresql',
'USER': '_user',
'PASSWORD': 'priv4te'
}
}
DATABASE_ROUTERS = [
'path.to.DatabaseRouter',
]
MIDDLEWARE = [
# middlewares
'path.to.RouterMiddleware'
]
最后更新 urls.py
:
urlpatterns = [
path('<str:client>/admin/', admin.site.urls),
path('<str:client>/', include('client_app.urls')),
# and so on
]
这种方法的优点是你不需要为新客户端配置任何东西,你需要做的就是在设置中添加一个新数据库,然后运行按照 documentation 中描述的迁移。无需配置反向代理服务器或其他任何东西。
现在,当谈到在 celery 中处理任务时,您可以提供您将使用哪个数据库来 运行 查询(参考 docs)。这是一个例子:
@app.task
def some_task():
logger.info("-"*25)
for db_name in settings.DATABASES.keys():
Model.objects.using(db_name).filter(some_condition=True)
logger.info("-"*25)
我有一个 Django 应用程序,它使用 Celery、RabbitMQ 和 Apache mod_wsgi。目前都在一台服务器上。每个客户端都有自己的 URL 挂载,例如:
每个客户都有自己的数据库和项目目录,其中 local_setting.py 用于他们的 Django 设置。
我正在使用 supervisord 为每个客户端管理 Celery Worker + Celery Beat。
随着我的客户越来越多,维护也越来越耗时。
我已经开始使用 Docker 来尝试简化部署,并可能在多个主机上进行扩展。
虽然为一个客户设置 Docker 组合 运行 一组服务非常容易,但我正在尝试为多个客户找出易于管理的最佳方法,例如快速设置安装在主 URL.
下的新客户端我认为应该共享 Postgres 数据库实例以保存每个客户端数据库,就像现在一样。并拥有一个共享的 NGIX 实例来处理 HTTP 端。对于每个客户端,使用一个 Kubernetes Pod,包括:
- Gunicorn 来处理 Django
- 芹菜节拍
- 芹菜工人
- 用于静态文件的轻量级 HTTP 服务器。
所以问题是,这是一个好方法还是有更好的方法来处理和处理这个问题?
我还想知道我是否应该为每个客户构建一个图像,因为这样可能更容易管理?
欢迎任何建议。
处理客户的三种方式:
- 孤立的方法:独立的数据库。每个租户都有自己的数据库。
- 半隔离方法:共享数据库,独立模式。所有租户一个数据库,但每个租户一个模式。
- 共享方法:共享数据库、共享架构。所有租户共享相同的数据库和模式。有一个 main tenant-table,所有其他表都有一个指向的外键。
django-tenents 包使用第二种方法,并有一个 sub-domains 用于客户端,例如 client1.example.com、client2.example.com 等
我在第三次使用了 django-tenants,为我创建的每个模型添加了一个 Company
模型外键。
django 租户有帮助,但在 postgres 中有不同的架构;开销集成较少。
添加 Company
必须在每个模型中实现,如果您使用的是基于 class 的视图,则应使用中间件或混入处理。
我的建议是为此保留一个代码库和一台服务器 运行ning(或同一 Django 应用程序的多个服务器,无需任何基于客户端的定制)。主要原因是为了维护更容易。您不希望多次更改以向多个客户端提供功能。
由于您已经有一个 Django 应用程序,我认为最好利用该代码来适应上面给出的方法,同时对代码进行最少的更改。意思是,您需要某种方法来处理连接到多个数据库的多个客户端。我建议是使用中间件和 database router. Like this:(codes based on this snippet).
import threading
request_cfg = threading.local()
class RouterMiddleware (object):
def process_view( self, request, view_func, args, kwargs ):
if 'client' in kwargs:
request_cfg.client = kwargs['client']
request.client = client
# Here, we are adding client info with request object.
# It will make the implementation bit easier because
# then you can access client information anywhere in the view/template.
def process_response( self, request, response ):
if hasattr( request_cfg, 'client' ):
del request_cfg.client
return response
class DatabaseRouter (object):
def _default_db( self ):
from django.conf import settings
if hasattr( request_cfg, 'client' ) and request_cfg.client in settings.DATABASES:
return request_cfg.client
else:
return None
def db_for_read( self, model, **hints ):
return self._default_db()
def db_for_write( self, model, **hints ):
return self._default_db()
然后将它们添加到 settings.py
:
DATABASES = {
'default': {
'NAME': 'user',
'ENGINE': 'django.db.backends.postgresql',
'USER': 'postgres_user',
'PASSWORD': 's3krit'
},
'client1': {
'NAME': 'client1',
'ENGINE': 'django.db.backends.postgresql',
'USER': 'postgres_user',
'PASSWORD': 's3krit'
},
'client2': {
'NAME': 'client2',
'ENGINE': 'django.db.backends.postgresql',
'USER': '_user',
'PASSWORD': 'priv4te'
}
}
DATABASE_ROUTERS = [
'path.to.DatabaseRouter',
]
MIDDLEWARE = [
# middlewares
'path.to.RouterMiddleware'
]
最后更新 urls.py
:
urlpatterns = [
path('<str:client>/admin/', admin.site.urls),
path('<str:client>/', include('client_app.urls')),
# and so on
]
这种方法的优点是你不需要为新客户端配置任何东西,你需要做的就是在设置中添加一个新数据库,然后运行按照 documentation 中描述的迁移。无需配置反向代理服务器或其他任何东西。
现在,当谈到在 celery 中处理任务时,您可以提供您将使用哪个数据库来 运行 查询(参考 docs)。这是一个例子:
@app.task
def some_task():
logger.info("-"*25)
for db_name in settings.DATABASES.keys():
Model.objects.using(db_name).filter(some_condition=True)
logger.info("-"*25)