gpt4 book ai didi

mysql - Multi-Tenancy Django 应用程序 : altering database connection per request?

转载 作者:IT老高 更新时间:2023-10-28 23:42:36 30 4
gpt4 key购买 nike

我正在寻找其他尝试使用数据库级隔离构建 Multi-Tenancy Django 应用程序的工作代码和想法。

更新/解决方案:我在一个新的开源项目中解决了这个问题:参见 django-db-multitenant

目标

我的目标是根据请求主机名或请求路径(例如 foo.example.com/将 Django 连接设置为使用数据库 foo,而 bar.example.com/ 使用数据库 bar)。

先例

我知道一些现有的 Django Multi-Tenancy 解决方案:

  1. django-tenant-schemas :这与我想要的非常接近:您以最高优先级安装其中间件,并将 SET search_path 命令发送到数据库。不幸的是,它是 Postgres 特有的,我被 MySQL 困住了。
  2. django-simple-multitenant :这里的策略是为所有模型添加一个“租户”外键,并调整所有应用程序业务逻辑以关闭它。基本上每一行都被 (id,tenant_id) 而不是 (id) 索引。我尝试过但不喜欢这种方法,原因有很多:它使应用程序更加复杂,可能导致难以发现的错误,而且它不提供数据库级别的隔离。
  3. 每个租户一个 {app server, django settings file with appropriate db}。又名穷人的 Multi-Tenancy (实际上是富人的,考虑到它所涉及的资源)。我不想为每个租户启动一个新的应用服务器,为了可扩展性,我希望任何应用服务器都能够为任何客户端分派(dispatch)请求。

想法

到目前为止,我最好的想法是执行 django-tenant-schemas 之类的操作:在第一个中间件中,获取 django.db.connection 并调整数据库选择而不是架构。我还没有完全考虑到这对于池/持久连接意味着什么

我追求的另一个死胡同是特定于租户的表前缀:除了我需要它们是动态的之外,即使是全局表前缀在 Django 中也不容易实现(参见 rejected ticket 5000 等)。

最后,Django multiple database support允许您定义多个命名数据库,并根据实例类型和读/写模式在它们之间多路复用。没有帮助,因为没有工具可以根据每个请求选择数据库。

问题

有人做过类似的事情吗?如果有,您是如何实现的?

最佳答案

我已经做了最接近第 1 点的类似操作,但没有使用中间件来设置默认连接,而是使用了 Django 数据库路由器。如果每个请求需要,这允许应用程序逻辑使用多个数据库。为每个查询选择合适的数据库取决于应用程序逻辑,这是这种方法的一大缺点。

通过此设置,所有数据库都列在 settings.DATABASES 中,包括可能在客户之间共享的数据库。每个客户特定的模型都放置在具有特定应用标签的 Django 应用中。

例如。下面的类定义了一个存在于所有客户数据库中的模型。

class MyModel(Model):
....
class Meta:
app_label = 'customer_records'
managed = False

一个数据库路由器被放置在 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)

关于这个路由器的特殊部分是 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)

这已经是一个相当复杂的设置了。它有效,但我会尝试总结一下我认为的优点和缺点:

优势

  • 数据库选择灵活。它允许在一个查询中使用多个数据库,客户特定数据库和共享数据库都可以在一个请求中使用。
  • 数据库选择是明确的(不确定这是优点还是缺点)。如果您尝试运行查询客户数据库但应用程序没有选择一个,则会发生异常,指示编程错误。
  • 使用数据库路由器允许不同的数据库存在于不同的主机上,而不是依赖于猜测所有数据库都可以通过单个连接访问的 USE db; 语句。

缺点

  • 它的设置很复杂,并且需要很多层才能使其正常运行。
  • 线程本地数据的需求和使用是模糊的。
  • View 中充斥着数据库选择代码。这可以使用基于类的 View 进行抽象,以与中间件选择默认数据库相同的方式根据请求参数自动选择数据库。
  • 选择数据库的上下文管理器必须以这样一种方式包裹查询集,即在评估查询时上下文管理器仍然处于事件状态。

建议

如果您想要灵活的数据库访问,我建议使用 Django 的数据库路由器。使用中间件或 View Mixin,它会根据请求参数自动设置用于连接的默认数据库。您可能不得不求助于线程本地数据来存储要使用的默认数据库,以便当路由器被击中时,它知道要路由到哪个数据库。这允许 Django 使用其现有的持久连接到数据库(如果需要,可以驻留在不同的主机上),并根据请求中设置的路由选择要使用的数据库。

这种方法还有一个优点,即如果需要,可以使用 QuerySet using() 覆盖查询的数据库。用于选择默认数据库以外的数据库的函数。

关于mysql - Multi-Tenancy Django 应用程序 : altering database connection per request?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16721051/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com