You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何配置Django Tenants结合默认路由实现只读副本报表读取

解决Django Tenants配合只读副本的路由配置问题

要让Django Tenants和只读副本协同工作,核心是在切换租户时同步设置主库和副本的schema,同时通过路由灵活控制读请求走向。以下是具体实现步骤:


1. 配置数据库连接(settings.py)

首先在配置中添加只读副本的连接,注意副本的数据库引擎必须使用django_tenants.postgresql_backend,确保schema功能正常:

DATABASES = {
    'default': {
        'ENGINE': 'django_tenants.postgresql_backend',
        'NAME': 'your_db_name',
        'USER': 'db_user',
        'PASSWORD': 'db_pass',
        'HOST': 'primary-db-host',
        'PORT': '5432',
    },
    'replica': {
        'ENGINE': 'django_tenants.postgresql_backend',
        'NAME': 'your_db_name',
        'USER': 'db_user',
        'PASSWORD': 'db_pass',
        'HOST': 'replica-db-host',
        'PORT': '5432',
        'OPTIONS': {
            # 可选:强制开启只读模式,避免误写
            'options': '-c default_transaction_read_only=on'
        }
    }
}

# 指定租户切换时的额外处理方法路径
EXTRA_SET_TENANT_METHOD_PATH = 'utils.tenant_utils.extra_set_tenant_stuff'

2. 实现租户切换的同步方法

创建utils/tenant_utils.py文件,编写extra_set_tenant_stuff方法——该方法会在Django Tenants切换租户时自动触发,用于同步设置副本的schema:

from django_tenants.utils import get_public_schema_name
from django.db import connections

def extra_set_tenant_stuff(tenant, schema_name):
    # 为主库设置schema(默认逻辑已处理,此处可省略,若需强校验可保留)
    if 'default' in connections:
        with connections['default'].cursor() as cursor:
            cursor.execute(f"SET search_path TO {schema_name}, {get_public_schema_name()}, public;")
    
    # 关键:为只读副本同步当前租户的schema
    if 'replica' in connections:
        try:
            with connections['replica'].cursor() as cursor:
                cursor.execute(f"SET search_path TO {schema_name}, {get_public_schema_name()}, public;")
        except Exception as e:
            # 副本连接失败时,关闭无效连接,后续请求自动重试或降级到主库
            connections['replica'].close()

3. 调整数据库路由逻辑

修改你的路由类,让报表相关的读请求走向副本,同时保留主库用于写操作和强一致性读:

class DatabaseRouter(object):
    def db_for_read(self, model, **hints):
        # 规则1:报表应用的所有读请求走副本
        if model._meta.app_label == 'reports':
            return 'replica'
        # 规则2:通过hints手动指定使用副本(适用于临时报表查询)
        if hints.get('use_replica'):
            return 'replica'
        # 默认读请求走主库(保证实时性)
        return 'default'

    def db_for_write(self, model, **hints):
        # 所有写操作强制走主库
        return 'default'

    def allow_relation(self, obj1, obj2, **hints):
        # 允许主库和副本之间的对象关联
        db_list = ('default', 'replica')
        if obj1._state.db in db_list and obj2._state.db in db_list:
            return True
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        # 仅主库执行迁移操作,副本无需同步迁移
        return db == 'default'

4. 报表查询的实际使用

在生成报表的代码中,直接指定使用副本数据库即可:

# 方式1:直接指定副本连接
from reports.models import Report
report_data = Report.objects.using('replica').filter(created_at__gte='2024-01-01')

# 方式2:通过hints传递(需路由支持)
qs = Report.objects.all()
qs.query.hints['use_replica'] = True
report_data = qs.filter(...)

注意事项

  • 确保PostgreSQL主从复制配置正确,副本的schema与主库实时同步
  • 副本不可用时,路由会自动降级到主库(得益于Django的数据库连接重试机制)
  • 对于需要强一致性的业务场景(如用户刚提交的数据查询),避免使用副本,保留主库读请求

内容的提问来源于stack exchange,提问作者iaggoCapitanio2

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 06:11:25