当前位置:首页 > 问答 > 正文

想搞清楚Python和Django咋整合Postgres+Citus实现分布式多租户数据库吗?

想搞清楚Python和Django咋整合Postgres+Citus实现分布式多租户数据库吗?这事儿说白了,就是把你的应用数据分散到多台机器上去存,让数据库能扛住更大的数据量和更多的用户访问,多租户就是一个系统能让很多不同的客户(租户)一起用,但他们的数据是相互隔开的,A公司看不到B公司的信息,下面我就掰开揉碎了给你讲讲怎么用Python和Django这套经典组合,把Citus这个分布式神器给用起来。

第一步:先整明白多租户是咋回事

多租户有好几种实现法子,选哪种决定了你后面怎么设计数据库,根据Citus官方文档的说法,主要分三种(来源:Citus官方文档 - Multi-Tenancy Applications):

  1. 一个租户一个数据库:隔离性最好,但数据库数量多了管理起来麻烦。
  2. 一个租户一个Schema:在同一个数据库里,给每个租户建一个独立的schema(可以理解成一个个小房间),Django对这个支持得挺好。
  3. 所有租户挤在一张表里:这是Citus最推荐、性能最好的方式,就是在一张大表里,用一个叫“租户ID”的字段来区分不同租户的数据,Citus会根据这个ID把数据分片,存到不同的机器上。

我们今天主要聊的就是第三种,也是最常用、最高效的那种。

第二步:准备好你的家伙事儿

在你开始写代码之前,得先把环境搭好,你需要安装几个东西(来源:Django官方文档 & Citus官方文档):

想搞清楚Python和Django咋整合Postgres+Citus实现分布式多租户数据库吗?

  • Python和Django:这个是你的老本行,用pip安装就行。
  • PostgreSQL:你得先有个基础的Postgres数据库。
  • Citus扩展:这才是关键,你需要安装Citus,并把它作为一个扩展(Extension)在你的Postgres数据库里启用,你可以选择自己部署一个Citus集群(有协调器节点和工作节点),或者直接用云服务商提供的托管Citus数据库,比如Azure Cosmos DB for PostgreSQL(原Citus Data)或一些云厂商的类似服务,这样能省去自己维护集群的麻烦。

安装好之后,你用psql连上数据库,跑一句 CREATE EXTENSION citus; 就启用它了。

第三步:配置Django的连接

你得告诉Django别连普通的PostgreSQL了,去连你的Citus数据库,Citus基本兼容PostgreSQL,所以连接方式差不多,打开Django的settings.py文件,找到DATABASES这个配置项,把它指向你的Citus协调器节点的地址、端口、数据库名、用户名和密码,看起来大概是这个样子:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'my_citus_database',
        'USER': 'my_user',
        'PASSWORD': 'my_password',
        'HOST': 'citus-coordinator-host.example.com', # 你的Citus协调器主机地址
        'PORT': '5432',
    }
}

第四步:设计你的数据模型,关键是那个“租户ID”

这是最核心的一步,假设我们做一个SaaS应用,有很多公司用,每个公司有自己的员工信息,你的Django模型里,每一张需要分片的表,都必须有一个字段来标记这条数据属于哪个租户,通常这个字段叫tenant_id或者company_id之类的。

想搞清楚Python和Django咋整合Postgres+Citus实现分布式多租户数据库吗?

举个例子,一个简单的员工模型:

from django.db import models
class Company(models.Model):
    name = models.CharField(max_length=100)
    # 这个表不需要分片,它可能很小,可以放在协调器节点上
class Employee(models.Model):
    tenant = models.ForeignKey(Company, on_delete=models.CASCADE) # 这就是我们的“租户ID”
    name = models.CharField(max_length=100)
    department = models.CharField(max_length=100)

注意看,Employee表里有一个指向Company模型的外键tenant,Citus就会根据这个tenant_id(数据库里实际存储的字段)来分发数据。

第五步:告诉Citus哪张表要分片,按什么分

光有模型不行,你得明确指示Citus对特定的表进行分布式处理,这个操作不能用Django的迁移命令(migrate)来完成,必须直接在你的Citus数据库上执行SQL命令(来源:Citus官方文档 - Defining Tables)。

psql或者其他工具连上你的Citus协调器节点,然后执行像这样的SQL:

想搞清楚Python和Django咋整合Postgres+Citus实现分布式多租户数据库吗?

-- 把 Employee 表对应的实际表(app_employee)设置成分布表
-- 指定分布列(distributed column)为 tenant_id
SELECT create_distributed_table('app_employee', 'tenant_id');
-- 如果你希望某个表的所有数据都放在协调器节点上(不分布),比如Company表,可以这么做
SELECT create_reference_table('app_company');

这里的app_employee是你的Django模型在数据库中生成的真实表名。create_distributed_table这个函数就是Citus的魔法,它告诉系统:“嘿,把app_employee表按照tenant_id这个字段的值进行分片,然后把不同的片存到不同的工作节点上去。”

第六步:在Django代码里怎么操作数据

搞定了上面的设置,你写Django代码的方式几乎不用变!这就是Django ORM的强大之处,你必须养成一个关键习惯:在查询分片表的时候,永远要带上租户ID

你要查某个公司下的所有员工,绝对不能直接Employee.objects.all(),这样Citus不知道你去哪个分片找,会扫描所有分片,效率极低,正确的做法是:

# 假设我们知道当前请求是来自 company_id 为 1 的公司
current_company_id = 1
# 好的做法:明确指定租户ID
employees = Employee.objects.filter(tenant_id=current_company_id)
# 或者通过关系查询
company = Company.objects.get(id=current_company_id)
employees = company.employee_set.all()

这样,Citus就能精准地把查询路由到存储了该公司数据的那一个分片上,速度飞快。

第七步:一些你可能遇到的坑和要注意的地方

  • 迁移文件:Django的makemigrationsmigrate命令依然可以用来创建表结构和字段变更,像create_distributed_table这样的分布式操作,必须手动执行SQL。
  • 事务:在Citus里,跨多个租户ID的写操作(比如给两个不同公司同时插入员工)可能无法放在一个数据库事务里,因为数据可能在不同的物理节点上,这点要特别注意。
  • 租户ID不能为空:你的应用逻辑要保证,在向分片表插入数据时,租户ID一定是有效的、非空的。
  • 性能:对于所有租户共享的、数据量小的表(比如国家列表、产品类别),可以用create_reference_table做成参考表,Citus会在每个工作节点上复制一份,提高关联查询速度。

用Python和Django整合Postgres+Citus搞分布式多租户,核心思路就几点:选对数据分布策略(按租户ID分片)、在模型里设计好租户ID字段、用SQL命令把表设置成分布式、写代码时时刻记得过滤租户ID,这套组合拳打好了,你的应用就能在数据量暴涨的时候依然稳如泰山,虽然中间有些步骤需要直接操作数据库,有点绕开了Django的ORM,但整体开发体验还是Django那套熟悉的味儿,上手起来并不难。