1. 概述:sqlalchemy 并没有像 django-orm 一样内置完善的读写分离方案, 但是提供了可以自定义的接口: 官方文档, 我们可以借此对 flask-sqlalchemy 进行二次开发, 实现读写分离
2. 基本实现思路:
实现自定义的 session类, 继承 SignallingSession类
重写 get_bind方法, 根据读写需求选择对应的数据库地址
实现自定义的 SQLAlchemy类, 继承 SQLAlchemy类
重写 create_session方法, 在内部使用自定义的 Session类
3. 环境准备:
搭建好两台数据库,1个主、2个从数据库,主数据库端口 3306
从数据库端口 8306,案例中由于我没有搭建两台服务器,只是使用了一台服务器的多个数据模拟多个服务器多个数据库
4. 二次开发代码实现:
import random
from flask_sqlalchemy import SQLAlchemy, SignallingSession, get_state
from flask import Flask
from sqlalchemy import orm, or_
# 1.创建app对象
app = Flask(__name__)
# 2.添加数据库配置信息
# app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://账号:密码@ip地址:端口/数据库名称"
# 单库连接信息
# app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+pymysql://root:mysql@192.168.243.157:3306/test39"
# 多库连接信息
app.config["SQLALCHEMY_BINDS"] = {
"master": "mysql+pymysql://root:149789@127.0.0.1:3306/master", # 主库
"slave1": "mysql+pymysql://root:149789@127.0.0.1:3306/slave1", # 从库1
"slave2": "mysql+pymysql://root:149789@127.0.0.1:3306/slave2", # 从库2
}
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
# app.config["SQLALCHEMY_ECHO"] = True
# 3.1 自定义session,继承于SignallingSession 重写get_bind方法,实现读写分离
class RoutingSession(SignallingSession):
def __init__(self, *args, **kwargs):
super(RoutingSession, self).__init__(*args, **kwargs)
def get_bind(self, mapper=None, clause=None):
"""每次数据库操作(增删改查及事务操作)都会调用该方法, 来获取对应的数据库引擎(访问的数据库)"""
state = get_state(self.app)
if mapper is not None:
try:
# SA >= 1.3
persist_selectable = mapper.persist_selectable
except AttributeError:
# SA < 1.3
persist_selectable = mapper.mapped_table
# 如果项目中指明了特定数据库,就获取到bind_key指明的数据库,进行数据库绑定
info = getattr(persist_selectable, 'info', {})
bind_key = info.get('bind_key')
if bind_key is not None:
return state.db.get_engine(self.app, bind=bind_key)
# 使用默认的主数据库
# SQLALCHEMY_DATABASE_URI 返回数据库引擎
# return SessionBase.get_bind(self, mapper, clause)
from sqlalchemy.sql.dml import UpdateBase
# 写操作 或者 更新 删除操作 - 访问主库
if self._flushing or isinstance(clause, UpdateBase):
print("写更新删除 访问主库")
# 返回主库的数据库引擎
return state.db.get_engine(self.app, bind="master")
else:
# 读操作--访问从库
slave_key = random.choice(["slave1", "slave2"])
print("读访问从库:{}".format(slave_key))
# 返回从库的数据库引擎
return state.db.get_engine(self.app, bind=slave_key)
# 3.2 自定义RoutingSQLAlchemy,继承于SQLAlchemy,重写写create_session,替换底层的SignallingSession
class RoutingSQLAlchemy(SQLAlchemy):
def create_session(self, options):
# 使用自定义实现了读写分离的RoutingSession
return orm.sessionmaker(class_=RoutingSession, db=self, **options)
# 3.3根据RoutingSQLAlchemy创建数据库对象
db = RoutingSQLAlchemy(app)
# db数据库功能: 1.模型类指定数据库查询 2.读写分离 3.定向查询[想查那个数据库只需要将数据库名字传入即可]
# 4.自定义模型类
# 构建模型类
class User(db.Model):
__tablename__ = 't_user'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20), unique=True)
age = db.Column(db.Integer, default=0, index=True)
@app.route('/')
def hello_world():
# 写操作 -- 访问主库
write_data()
# 更新操作 -- 访问主库
update_data()
# 读操作 -- 访问从库
read_data()
return 'Hello World!'
def write_data():
user1 = User(name="xiaoming11", age=19, id=11)
db.session.add(user1)
db.session.commit()
def update_data():
User.query.filter(User.name == "xiaoming11").update({"name": "curry11"})
db.session.commit()
def read_data():
user1 = User.query.filter(or_(User.name == "admin111", User.name == "admin222")).first()
print(user1, user1.name, user1.id, user1.age)
if __name__ == '__main__':
app.run(debug=True, port=8000)
5. 程序启动,访问输出
* Running on http://127.0.0.1:8000/ (Press CTRL+C to quit)
写更新删除 访问主库
写更新删除 访问主库
写更新删除 访问主库
读访问从库:slave1
<User 111> admin111 111 111
127.0.0.1 - - [14/May/2021 10:41:15] "?[37mGET / HTTP/1.1?[0m" 200 -
在访问一次做测试:
* Running on http://127.0.0.1:8000/ (Press CTRL+C to quit)
写更新删除 访问主库
写更新删除 访问主库
写更新删除 访问主库
读访问从库:slave2
<User 222> admin222 222 222
127.0.0.1 - - [14/May/2021 10:52:35] "?[37mGET / HTTP/1.1?[0m" 200 -