PostgreSQL只读副本读请求强制:属应用层任务?Django/Flask自动方案?
嘿,我来帮你把这两个问题讲清楚,结合Django和Flask的实际用法来给你梳理:
一、Django/Flask如何自动路由读请求到PostgreSQL只读副本
Django 原生实现自动读写分离
Django其实自带了读写分离的支持,完全不用手动每次指定连接,框架会自动根据操作类型路由请求,配置步骤如下:
- 在
settings.py里配置主库和只读副本的数据库信息:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'your_db_name', 'USER': 'main_db_user', 'PASSWORD': 'main_db_pass', 'HOST': 'main-db-host', 'PORT': '5432', }, 'read_replica': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'your_db_name', # 和主库同名,因为是副本 'USER': 'read_only_user', 'PASSWORD': 'read_only_pass', 'HOST': 'replica-db-host', 'PORT': '5432', 'TEST': { 'MIRROR': 'default', # 测试环境复用主库的测试数据,避免重复初始化 } } }
- 写一个数据库路由类,告诉Django怎么分配读写请求
在项目根目录创建routers.py文件,写入路由逻辑:
class ReadReplicaRouter: def db_for_read(self, model, **hints): # 所有读操作优先走只读副本 return 'read_replica' def db_for_write(self, model, **hints): # 所有写操作必须走主库 return 'default' def allow_relation(self, obj1, obj2, **hints): # 允许主库和副本之间的关联关系(比如外键) allowed_dbs = ('default', 'read_replica') if obj1._state.db in allowed_dbs and obj2._state.db in allowed_dbs: return True return None def allow_migrate(self, db, app_label, model_name=None, **hints): # 只在主库执行数据库迁移,副本不需要做迁移 return db == 'default'
- 在
settings.py里注册这个路由类:
DATABASE_ROUTERS = ['your_project_name.routers.ReadReplicaRouter']
配置完成后,Django会自动处理:Model.objects.get()、filter()、all()这类读操作会自动用read_replica连接;save()、create()、delete()这类写操作则走default主库。如果遇到特殊场景(比如刚写完数据需要立即读取最新值,避免副本同步延迟),可以手动指定走主库:
# 强制从主库读取数据 latest_obj = Model.objects.using('default').get(pk=1)
Flask 实现自动读写分离
Flask本身没有原生的读写分离功能,但可以通过Flask-SQLAlchemy的绑定功能或者第三方库来实现自动路由,这里给你两种常用方案:
方案1:基于Flask-SQLAlchemy的自定义路由
- 先配置多个数据库连接:
from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) # 默认连接主库 app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://main_user:main_pass@main-db-host:5432/your_db' # 绑定只读副本 app.config['SQLALCHEMY_BINDS'] = { 'read_replica': 'postgresql://read_user:read_pass@replica-db-host:5432/your_db' } db = SQLAlchemy(app)
- 封装自动读路由的逻辑
可以自定义一个Query类,让读操作默认使用副本连接:
from sqlalchemy.orm import Query class ReadReplicaQuery(Query): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 读操作默认绑定副本连接 self = self.with_session(db.get_engine(app, bind='read_replica').connect()) # 定义抽象基类,让所有模型继承它 class BaseModel(db.Model): __abstract__ = True query_class = ReadReplicaQuery # 你的业务模型继承BaseModel class YourModel(BaseModel): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100))
这样,平时的读操作(比如YourModel.query.all())会自动走副本;写操作直接用默认的db.session就会走主库:
# 写操作,自动走主库 new_item = YourModel(name="test") db.session.add(new_item) db.session.commit()
如果需要强制读主库,手动指定连接即可:
# 强制从主库读取 item = db.session.query(YourModel).with_session(db.get_engine(app).connect()).get(1)
方案2:使用第三方库sqlalchemy-replication
这个库专门用来处理SQLAlchemy的主从读写分离,配置更省心:
from sqlalchemy import create_engine from sqlalchemy_replication import load_balanced_session, MasterSlaveConnectionPool # 创建主从连接池,主库地址写在前面,后面跟副本地址 engine = create_engine( 'postgresql://main_user:main_pass@main-db-host:5432/your_db', poolclass=MasterSlaveConnectionPool, slaves=[ 'postgresql://read_user:read_pass@replica-db-host:5432/your_db' ] ) # 创建自动路由的session Session = load_balanced_session(bind=engine) session = Session() # 读操作自动走副本,写操作自动走主库 all_items = session.query(YourModel).all() # 走副本 session.add(YourModel(name="new test")) # 走主库 session.commit()
二、强制读请求路由至只读副本是否属于应用层任务?
答案是肯定的,这属于应用层的职责,原因主要有这几点:
- 数据库主从同步只是完成数据层面的复制,它不会主动帮应用分流请求;
- 应用层最清楚当前操作的属性:是读还是写,是否需要读取最新数据(比如刚写完就读的场景,必须走主库避免同步延迟),可以做更灵活的定制;
- 虽然有些中间件(比如PgBouncer)能做简单的读写分流,但灵活性远不如应用层控制——比如无法针对特定模型、特定请求做个性化路由。
至于你提到的“自动管理连接路由,无需手动指定”,Django原生就支持这个功能;Flask虽然需要额外配置或借助扩展,但也能实现不用每次手动选连接的效果,框架或封装的逻辑会自动根据操作类型选择对应的数据库连接。
内容的提问来源于stack exchange,提问作者Subtubes
相关产品推荐
相关产品推荐

