如何在Django中为每个“应用实例”使用不同的数据库?

8 投票
2 回答
3899 浏览
提问于 2025-04-17 05:26

场景介绍

我们有两个应用程序。

TheApp

TheApp 是一个非常棒的应用,客户们都很喜欢。每个客户都有自己独立的实例,这意味着每个客户使用不同的数据库(包括名称、用户名和密码)。数据库的连接方式应该根据请求来自哪个域来决定。

req: customerA.foo.tld -> db:(app_cust1, cust1, hunter2)
req: customerB.foo.tld -> db:(app_cust2, cust2, hunter3)

管理应用程序

这个应用程序应该能够为客户创建或删除 TheApp 的实例。因此,它需要设置新的数据库,并把配置信息写到某个地方。决定哪个数据库用于处理请求的方式应该高效且易于管理。

问题

如何选择一个实例应该使用哪个数据库连接?哪种方式性能最好?哪种方式扩展性最好?

我想到的几种方法™

我看了一些资料,以下是我想到的几种方法:

(wsgi 守护进程 + settings.py) 每个实例

每个客户会有自己的 settings.py 文件,里面包含数据库的凭证。这些设置可以从一个共享的设置文件中继承一些公共内容。

每当有新的设置文件时,都需要启动一个新的 wsgi 实例。如果客户数量很多,这样的扩展性可能不好。而且创建 apache 虚拟主机文件也很麻烦。

使用 'using' 和一个 settings.py

我可以这样做:

MyModel.objects.using(THE_CURRENT_DB).all()

并在每个请求中设置 THE_CURRENT_DB(可能是中间件的东西?)。但这样在各处都要做这件事,看起来有点麻烦。而且每当客户获得自己的实例时,settings.py/app 也得重写。

一个 settings.py + 应用路由器

我还没有查看能否在路由器中访问请求的任何信息,但如果可以的话,我也许可以决定在 settings.py 中使用哪个数据库。就像 这个链接 中的例子,但不是按模型,而是按请求。

在中间件中修改设置

我刚想到,或许可以在中间件中修改数据库的设置。我还没有查看 Django 中间件是如何工作的,以及可以做些什么。

一些不太常见的其他方法

因为我对 Django 还很陌生,可能遗漏了一些要点,或者有些想法完全不靠谱。你会怎么做呢?

为什么不把所有东西放在一个数据库里?

因为我觉得把东西分开是好的。如果出现问题,不会所有人都受到影响。

2 个回答

1

这是一个展示Django配置模块(设置)弱点的场景。没有“Django官方支持”的方法来解决这个问题。

我认为你可以选择一种对代码维护和可移植性影响最小的方案。所以我建议:

  • 使用中间件来将用户的配置保存在某种数据结构中(这是Django支持的)
  • 让settings.DATABASE变成一个可以调用的函数,从上面的数据结构中获取用户的配置(这算是Django的一个小技巧)
  • 利用Django的多数据库功能来访问模型(这是Django支持的)
2

这可以通过中间件和Postgres的命名空间轻松实现。下面是一个简单粗暴的例子,没有任何错误处理:

class NamespaceMiddleware:
    def process_request(self, request):

        # Get the subdomain. You could also use the domain name, but you'll have to remove special characters.
        host = request.get_host()
        parts = host.split('.')
        if len(parts) >= 3:
            subdomain = parts[0]

        # Set the namespace (aka "schema"). This will throw a DatabaseError if the namespace does not exist.
        from django.db import connection
        cursor = connection.cursor()
        cursor.execute("SET search_path TO ", subdomain)

启用这个中间件后,每个客户的数据可以完全独立,不需要额外的操作就能实现。不过,有几个事情需要注意:

  1. 一个问题是在开发过程中如何处理这个。我通常会加一个条件语句,如果设置中的DEBUG为True,就忽略上面的过程。不过你也可以设置虚拟主机,并编辑你的hosts文件来在开发中测试这个。
  2. 另一个需要考虑的是,你必须为每个实例运行一个单独的虚拟主机。否则,你可能会遇到实例数据交叉的问题。我猜这可能是某种线程问题,但比我聪明的人可能能更详细地解释这个。
  3. 最后,你需要考虑如何处理新的安装和模式更新。Django会把所有东西放在公共模式中。你需要学习如何复制这个模式来创建一个新的模式,并且还要习惯于编写数据库更新的脚本。

撰写回答