2012-01-20 10 views
19

Tengo una aplicación web Flask, SQLAlchemy que utiliza un solo servidor mysql. Quiero ampliar la configuración de la base de datos para que tenga un servidor esclavo de solo lectura, de modo que pueda distribuir las lecturas entre el maestro y el esclavo mientras continúo escribiendo en el servidor maestro de DNS.read slave, configuración maestra de lectura-escritura

He visto algunas opciones y creo que no puedo hacer esto con SQLAlchemy simple. En su lugar, estoy planeando crear 2 identificadores de base de datos en mi aplicación web, uno para los servidores db maestros y esclavos. Luego, usando un valor aleatorio simple, use el control maestro/esclavo db para operaciones "SELECT".

Sin embargo, no estoy seguro de si este es el camino correcto para usar SQLAlchemy. Alguna sugerencia/consejos sobre cómo sacar esto? Gracias por adelantado.

Respuesta

26

Tengo un ejemplo de cómo hacer esto en mi blog en http://techspot.zzzeek.org/2012/01/11/django-style-database-routers-in-sqlalchemy/. Básicamente puede mejorar la sesión para que elija entre maestro o esclavo consulta por consulta. Un problema potencial con ese enfoque es que si tiene una transacción que llama a seis consultas, puede terminar usando ambos esclavos en una solicitud ... pero ahí estamos tratando de imitar la función de Django :)

A ligeramente enfoque menos mágica que también establece el alcance del uso de manera más explícita que he usado es un decorador de vista callables (como se llamen en el frasco), así:

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

with_slave haría algo como esto, suponiendo que tiene una sesión y algunos motores configurados:

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 

La idea es que llamar al Session(bind=slave) invoca el registro para obtener el objeto de sesión actual para el hilo actual, crearlo si no existe; sin embargo, dado que estamos pasando un argumento, scoped_session afirmará que la sesión que estamos hacer aquí es definitivamente nuevo.

Lo señala en el "esclavo" para todos los SQL posteriores. Luego, cuando la solicitud finalice, se aseguraría de que su aplicación Flask llame al Session.remove() para borrar el registro de esa cadena. Cuando el registro se usa a continuación en el mismo hilo, será una nueva sesión vinculada al "maestro".

o una variante, que desea utilizar el "esclavo" sólo por esa llamada, esto es "más seguro", ya que restaura cualquier vinculación existente La vuelta a la Sesión:

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 

Para cada uno de estos decoradores puede invertir las cosas, haga que la sesión se vincule a un "esclavo" donde el decorador lo pone en "maestro" para las operaciones de escritura. Si quisieras un esclavo aleatorio en ese caso, si Flask tuviera algún tipo de evento de "solicitud para comenzar", podrías configurarlo en ese punto.

+2

Thnx zzzeek Esto ayuda mucho. Felicitaciones a todo el trabajo increíble en sqlalchemy. –

+0

¡Comentario de Rad, ejemplos de código geniales también! Sería bueno si sqlalchemy tuviera alguna manera de hacer análisis de consultas y enrutar automáticamente, pero en un mundo donde una consulta puede causar una tabla tmp u otra operación de escritura como resultado de lo que presumiblemente normalmente solo sería leído, requeriría algo así como solicitar el plan de consulta desde el back-end antes de enviar la consulta y sería más complicado de lo que sería en la mayoría de los casos. –

+1

tenemos la opción de "análisis de consultas", aunque requiere que usted mismo escriba el análisis. El sistema de fragmentación horizontal ilustra un ejemplo de este tipo de técnica, consulte http://docs.sqlalchemy.org/en/rel_0_7/orm/extensions/horizontal_shard.html. – zzzeek

0

O bien, podemos intentarlo de otra manera. Tal como podemos declarar dos clases diferentes con todos los atributos de instancia iguales pero el atributo de clase __bind__ es diferente. Por lo tanto, podemos utilizar rw clase para leer/escribir y r clase para hacer solo lectura. :)

Creo que de esta manera es más fácil y confiable. :)

Declaramos dos modelos db porque podemos tener tablas en dos db diferentes con los mismos nombres. De esta forma también podemos omitir el error "extender_existir" cuando dos modelos con el mismo __tablename__.

Aquí se muestra un ejemplo:

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'  
+0

puede mejorar su respuesta proporcionando un ejemplo de creación, definición y uso de dos bases de datos con accesibilidad de lectura y escritura diferentes –