如何配置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
相关产品推荐
相关产品推荐

