it-swarm.com.de

Wie kann man SQLAlchemy in Tornado asynchron machen?

Wie mache ich SQLAlchemy in Tornado zu async? Ich habe ein Beispiel für MongoDB auf async mongo example gefunden, aber ich konnte nichts wie motor für SQLAlchemy finden. Weiß jemand, wie man SQLAlchemy Abfragen zur Ausführung mit tornado.gen Erstellt (ich verwende MySQL unter SQLAlchemy, im Moment liest mein Handler aus der Datenbank und kehrt zurück Als Ergebnis möchte ich dies asynchron machen).

43
Damir

ORMs eignen sich schlecht für die explizite asynchrone Programmierung, bei der der Programmierer explizite Rückrufe immer dann erstellen muss, wenn etwas passiert, das den Netzwerkzugriff verwendet. Ein Hauptgrund dafür ist, dass ORMs das Lazy Loading - Muster ausgiebig verwenden, was mit explizitem Async mehr oder weniger inkompatibel ist. Code, der so aussieht:

user = Session.query(User).first()
print user.addresses

es werden zwei separate Abfragen ausgegeben - eine, wenn Sie first() sagen, um eine Zeile zu laden, und die nächste, wenn Sie user.addresses sagen, falls die .addresses - Auflistung nicht vorhanden ist. nicht bereits vorhanden oder abgelaufen Im Grunde blockiert fast jede Codezeile, die sich mit ORM-Konstrukten befasst, E/A, sodass Sie innerhalb von Sekunden in umfangreichen Rückruf-Spaghetti sind - und um das Ganze noch schlimmer zu machen, wird die überwiegende Mehrheit dieser Codezeilen nicht Block auf E/A, so dass der gesamte Aufwand für das Verbinden von Rückrufen für ansonsten einfache Attributzugriffsoperationen Ihr Programm erheblich weniger effizient macht.

Ein Hauptproblem bei expliziten asynchronen Modellen besteht darin, dass sie komplexen Systemen einen enormen Funktionsaufruf-Overhead Python hinzufügen - nicht nur auf der dem Benutzer zugewandten Seite wie beim verzögerten Laden, sondern auch auf der internen Seite in Bezug auf das System Bietet Abstraktion um die Datenbank-API Python (DBAPI). Wenn SQLAlchemy auch nur über eine grundlegende asynchrone Unterstützung verfügt, würde dies für die überwiegende Mehrheit der Programme, die keine asynchronen Muster verwenden, und selbst für die asynchronen Programme, die nicht in hohem Maße gleichzeitig ausgeführt werden, erhebliche Leistungseinbußen nach sich ziehen. Betrachten Sie SQLAlchemy oder eine andere ORM- oder Abstraktionsschicht, die möglicherweise folgenden Code enthält:

def execute(connection, statement):
     cursor = connection.cursor()
     cursor.execute(statement)
     results = cursor.fetchall()
     cursor.close()
     return results

Der obige Code führt eine scheinbar einfache Operation aus und führt eine SQL-Anweisung für eine Verbindung aus. Bei Verwendung einer vollständig asynchronen DBAPI wie der asynchronen Erweiterung von psycopg2 wird der obige Code jedoch mindestens dreimal auf IO blockiert. Wenn Sie also den obigen Code in einem expliziten asynchronen Stil schreiben, bedeutet dies, dass der obige äußere Funktionsaufruf mindestens drei Funktionsaufrufe anstelle von einem enthält, ohne den auferlegten Overhead durch das explizite asynchrone System oder das DBAPI ruft sich selbst auf. So erhält eine einfache Anwendung automatisch die dreifache Strafe für den Funktionsaufruf-Overhead, der eine einfache Abstraktion um die Anweisungsausführung herum verursacht. Und in Python Funktionsaufruf-Overhead ist alles .

Aus diesen Gründen bin ich nach wie vor wenig begeistert von dem Hype um explizite asynchrone Systeme, zumindest in dem Maße, in dem einige Leute scheinbar für alles asynchron bleiben möchten, wie zum Beispiel für die Bereitstellung von Webseiten (siehe node.js). Ich würde empfehlen, stattdessen implizite asynchrone Systeme zu verwenden, insbesondere gevent , bei denen Sie alle nicht blockierenden IO Vorteile eines asynchronen Modells und keine der strukturellen Ausführlichkeiten/Nachteile expliziter Rückrufe erhalten . Ich versuche weiterhin, Anwendungsfälle für diese beiden Ansätze zu verstehen, und bin daher verwirrt über die Attraktivität des expliziten asynchronen Ansatzes als Lösung für alle Probleme, dh wie Sie bei node.js sehen - wir verwenden Skriptsprachen in der Die erste Möglichkeit, die Ausführlichkeit und die Komplexität des Codes zu reduzieren, und die explizite Asynchronisierung für einfache Dinge wie das Bereitstellen von Webseiten, scheint nichts anderes zu tun, als ein Boilerplate hinzuzufügen, das genauso gut durch gevent oder ähnliches automatisiert werden kann, wenn das Blockieren von IO sogar so ist ein Problem in einem solchen Fall (viele hochvolumige Websites eignen sich gut für ein synchrones IO -Modell). Gevent-basierte Systeme sind produktionserprobt und erfreuen sich wachsender Beliebtheit. Wenn Sie also die von ORMs bereitgestellte Code-Automatisierung mögen, möchten Sie möglicherweise auch die Async-IO-Scheduling-Automatisierung nutzen, die ein System wie gevent bietet.

Update : Nick Coghlan wies auf sein großartiger Artikel zum Thema explizite vs. implizite asynchrone hin, das auch hier zu lesen ist . Und ich wurde auch auf die Tatsache aktualisiert, dass pep-3156 begrüßt jetzt die Interoperabilität mit gevent , was sein zuvor erklärtes Desinteresse an gevent weitgehend dank Nicks Artikel umkehrt. Daher würde ich in Zukunft einen Hybrid von Tornado empfehlen, der gevent für die Datenbanklogik verwendet, sobald das System zur Integration dieser Ansätze verfügbar ist.

74
zzzeek

Ich hatte das gleiche Problem in der Vergangenheit und konnte keine zuverlässige Async-MySQL-Bibliothek finden. Es gibt jedoch eine coole Lösung mit Asyncio + Postgres . Sie müssen nur die Bibliothek aiopg verwenden, die standardmäßig mit SQLAlchemy-Unterstützung geliefert wird:

import asyncio
from aiopg.sa import create_engine
import sqlalchemy as sa


metadata = sa.MetaData()

tbl = sa.Table('tbl', metadata,
           sa.Column('id', sa.Integer, primary_key=True),
           sa.Column('val', sa.String(255)))

@asyncio.coroutine
def go():
    engine = yield from create_engine(user='aiopg',
                                      database='aiopg',
                                      Host='127.0.0.1',
                                      password='passwd')

    with (yield from engine) as conn:
        yield from conn.execute(tbl.insert().values(val='abc'))

        res = yield from conn.execute(tbl.select().where(tbl.c.val=='abc'))
        for row in res:
            print(row.id, row.val)


loop = asyncio.get_event_loop()
loop.run_until_complete(go())
24
Ander

Kein Tornado, aber wir haben SQLAlchemy im GINO-Projekt sozusagen asynchronisiert:

import asyncio
from gino import Gino, enable_task_local
from sqlalchemy import Column, Integer, Unicode, cast

db = Gino()


class User(db.Model):
    __table= 'users'

    id = Column(Integer(), primary_key=True)
    nickname = Column(Unicode(), default='noname')


async def main():
    await db.create_pool('postgresql://localhost/gino')

    # Create object, `id` is assigned by database
    u1 = await User.create(nickname='fantix')
    print(u1.id, u1.nickname)  # 1 fantix

    # Retrieve the same row, as a different object
    u2 = await User.get(u1.id)
    print(u2.nickname)  # fantix

    # Update affects only database row and the operating object
    await u2.update(nickname='daisy')
    print(u2.nickname)  # daisy
    print(u1.nickname)  # fantix

    # Returns all user objects with "d" in their nicknames
    users = await User.query.where(User.nickname.contains('d')).gino.all()

    # Find one user object, None if not found
    user = await User.query.where(User.nickname == 'daisy').gino.first()

    # Execute complex statement and return command status
    status = await User.update.values(
        nickname='No.' + cast(User.id, Unicode),
    ).where(
        User.id > 10,
    ).gino.status()

    # Iterate over the results of a large query in a transaction as required
    async with db.transaction():
        async for u in User.query.order_by(User.id).gino.iterate():
            print(u.id, u.nickname)


loop = asyncio.get_event_loop()
enable_task_local(loop)
loop.run_until_complete(main())

Es sieht ein bisschen so aus, aber tatsächlich ganz anders als SQLAlchemy ORM. Weil wir nur einen Teil des SQLAlchemy-Kerns verwendet und darauf ein einfaches ORM erstellt haben. Darunter wird asyncpg verwendet, daher ist es nur für PostgreSQL .

Update : GINO unterstützt Tornado jetzt dank des Beitrags von Vladimir Goncharov. Siehe docs here

4
Fantix King

Ich verwende Tornado mit SQLalchemy auf folgende Weise:


from tornado_mysql import pools
from sqlalchemy.sql import table, column, select, join
from sqlalchemy.dialects import postgresql, mysql

# from models import M, M2

t = table(...)
t2 = table(...)

xxx_id = 10

j = join(t, t2, t.c.t_id == t2.c.id)
s = select([t]).select_from(j).where(t.c.xxx == xxx_id)

sql_str = s.compile(dialect=mysql.dialect(),compile_kwargs={"literal_binds": True})


pool = pools.Pool(conn_data...)
cur = yield pool.execute(sql_str)
data = cur.fetchone()

In diesem Fall können wir sqlalchemy-Modelle und sqlalchemy-Tools für die Erstellung von Abfragen verwenden.

3