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 -