27

我有一个使用单个 mysql 服务器的 Flask,SQLAlchemy webapp。我想扩展数据库设置以拥有一个只读的从服务器,这样我就可以在主从服务器之间传播读取,同时继续写入主数据库服务器。

我看过几个选项,我相信我不能用普通的 SQLAlchemy 做到这一点。相反,我计划在我的 webapp 中创建 2 个数据库句柄,一个用于主数据库服务器和从属数据库服务器。然后使用简单的随机值使用主/从数据库句柄进行“选择”操作。

但是,我不确定这是否是使用 SQLAlchemy 的正确方法。关于如何解决这个问题的任何建议/提示?

4

2 回答 2

41

我的博客http://techspot.zzzeek.org/2012/01/11/django-style-database-routers-in-sqlalchemy/上有一个如何执行此操作的示例。基本上,您可以增强 Session 以便它在逐个查询的基础上从主服务器或从服务器中进行选择。这种方法的一个潜在故障是,如果您有一个调用六个查询的事务,您最终可能会在一个请求中同时使用两个从属设备……但我们只是在尝试模仿 Django 的功能:)

一种稍微不那么神奇的方法,也更明确地确定了我使用的使用范围,是视图可调用对象(无论它们在 Flask 中调用什么)的装饰器,如下所示:

@with_slave
def my_view(...):
   # ...

with_slave 会做这样的事情,假设你有一个 Session 和一些引擎设置:

master = create_engine("some DB")
slave = create_engine("some other DB")
Session = scoped_session(sessionmaker(bind=master))

def with_slave(fn):
    def go(*arg, **kw):
        s = Session(bind=slave)
        return fn(*arg, **kw)
    return go

这个想法是调用Session(bind=slave)调用注册表来获取当前线程的实际 Session 对象,如果它不存在则创建它 - 但是由于我们正在传递一个参数,scoped_session 将断言我们在这里创建的 Session 是绝对是全新的。

您将它指向所有后续 SQL 的“从属”。然后,当请求结束时,您将确保您的 Flask 应用程序正在调用Session.remove()以清除该线程的注册表。当注册表下次在同一个线程上使用时,它将是一个绑定回“主”的新会话。

或者一个变体,你想只为那个调用使用“从属”,这是“更安全”的,因为它将任何现有的绑定恢复回会话:

def with_slave(fn):
    def go(*arg, **kw):
        s = Session()
        oldbind = s.bind
        s.bind = slave
        try:
            return fn(*arg, **kw)
        finally:
            s.bind = oldbind
    return go

对于这些装饰器中的每一个,您都可以反转事物,将 Session 绑定到一个“从属”,装饰器将其置于“主”以进行写操作。如果在这种情况下你想要一个随机的奴隶,如果 Flask 有某种“请求开始”事件,你可以在那个时候设置它。

于 2012-01-24T01:36:21.727 回答
2

或者,我们可以尝试另一种方式。例如我们可以声明两个不同的类,所有实例属性相同但__bind__类属性不同。因此,我们可以使用rw类进行读/写,使用r类进行只读。:)

我认为这种方式更简单可靠。:)

我们声明了两个数据库模型,因为我们可以在两个不同的数据库中拥有相同名称的表。这样,当两个模型具有相同的__tablename__时,我们也可以绕过“extend_existing”错误。

这是一个例子:

app = Flask(__name__)
app.config['SQLALCHEMY_BINDS'] = {'rw': 'rw', 'r': 'r'}
db = SQLAlchemy(app)
db.Model_RW = db.make_declarative_base()

class A(db.Model):
    __tablename__ = 'common'
    __bind_key__ = 'r'

class A(db.Model_RW):
    __tablename__ = 'common'
    __bind_key__ = 'rw'    
于 2016-05-12T03:49:22.063 回答