mik*_*k3y 31 mysql django multi-tenant
我正在寻找其他人尝试使用数据库级隔离来构建多租户Django应用程序的工作代码和想法.
更新/解决方案:我在一个新的开源项目中结束了解决这个问题:请参阅django-db-multitenant
我的目标是在请求进入单个应用服务器(WSGI前端,如gunicorn)时根据请求主机名或请求路径多路复用请求(例如,foo.example.com/将Django连接设置为使用数据库foo,并bar.example.com/使用数据库bar).
我知道Django中有一些现有的多租户解决方案:
SET search_pathdb 发送命令.不幸的是,这是Postgres特有的,我坚持使用MySQL.(id, tenant_id)而不是(id).我尝试过,并且不喜欢这种方法有很多原因:它使应用程序更复杂,可能导致难以发现的错误,并且它不提供数据库级别的隔离.到目前为止,我最好的想法是做一些类似的事情django-tenant-schemas:在第一个中间件中,抓住django.db.connection并摆弄数据库选择而不是模式.在集合/持久连接方面,我还没有想过这意味着什么
我追求的另一个死胡同是特定于租户的表前缀:除了我需要它们是动态的,即使是Django中也不容易实现全局表前缀(参见被拒绝的票证5000等).
最后,Django 多数据库支持允许您定义多个命名数据库,并根据实例类型和读/写模式在它们之间进行多路复用.没有帮助,因为没有工具可以按请求选择数据库.
有没有人管理类似的东西?如果是这样,你是如何实现它的?
Aus*_*ips 14
我做了类似于最接近第1点的类似事情,但不使用中间件来设置默认连接,而是使用Django数据库路由器.这允许应用程序逻辑在每个请求需要时使用多个数据库.应用程序逻辑可以为每个查询选择合适的数据库,这是这种方法的一大缺点.
通过此设置,列出了所有数据库settings.DATABASES,包括可以在客户之间共享的数据库.每个客户特定的模型都放在具有特定应用标签的Django应用中.
例如.以下类定义了存在于所有客户数据库中的模型.
class MyModel(Model):
....
class Meta:
app_label = 'customer_records'
managed = False
Run Code Online (Sandbox Code Playgroud)
数据库路由器放在settings.DATABASE_ROUTERS链中以路由数据库请求app_label,如下所示(不是完整示例):
class AppLabelRouter(object):
def get_customer_db(self, model):
# Route models belonging to 'myapp' to the 'shared_db' database, irrespective
# of customer.
if model._meta.app_label == 'myapp':
return 'shared_db'
if model._meta.app_label == 'customer_records':
customer_db = thread_local_data.current_customer_db()
if customer_db is not None:
return customer_db
raise Exception("No customer database selected")
return None
def db_for_read(self, model, **hints):
return self.get_customer_db(model, **hints)
def db_for_write(self, model, **hints):
return self.get_customer_db(model, **hints)
Run Code Online (Sandbox Code Playgroud)
有关此路由器的特殊部分是thread_local_data.current_customer_db()呼叫.在执行路由器之前,调用者/应用程序必须已设置当前客户数据库thread_local_data.Python上下文管理器可用于此目的来推送/弹出当前客户数据库.
通过所有这些配置,应用程序代码看起来像这样,其中UseCustomerDatabase是一个上下文管理器,用于将当前客户数据库名称推送/弹出,thread_local_data以便thread_local_data.current_customer_db()在最终命中路由器时返回正确的数据库名称:
class MyView(DetailView):
def get_object(self):
db_name = determine_customer_db_to_use(self.request)
with UseCustomerDatabase(db_name):
return MyModel.object.get(pk=1)
Run Code Online (Sandbox Code Playgroud)
这已经非常复杂了.它有效,但我会试着总结一下我所看到的优点和缺点:
好处
USE db;猜测所有数据库都可通过单个连接访问的语句.缺点
建议
如果您想要灵活的数据库访问,我建议使用Django的数据库路由器.使用中间件或视图Mixin,它根据请求参数自动设置用于连接的默认数据库.您可能不得不求助于线程本地数据来存储要使用的默认数据库,以便在命中路由器时,它知道要路由到哪个数据库.这允许Django使用其与数据库的现有持久连接(如果需要,可以驻留在不同的主机上),并根据请求中设置的路由选择要使用的数据库.
此方法还具有以下优点:如果需要,可以使用该QuerySet using()函数选择除默认值之外的数据库来覆盖查询的数据库.
作为记录,我选择实现我的第一个想法的变体:USE <dbname>在早期请求中间件中发出 a。我也以同样的方式设置 CACHE 前缀。
我在一个小型生产站点上使用它,根据请求主机从 Redis 数据库中查找租户名称。到目前为止,我对结果非常满意。
我已经将它变成了一个(希望可重新启动的)github 项目: https: //github.com/mik3y/django-db-multitenant