2016-10-01 35 views
4

我想绕过Tornado和异步连接到Postgresql我的头。我找到了一个可以在http://peewee-async.readthedocs.io/en/latest/上执行此操作的库。peewee和peewee异步:为什么异步较慢

我设计了一个小测试来比较传统的Peewee和Peewee异步,但不知何故,异步运行速度较慢。

这是我的应用程序:

import peewee 
import tornado.web 
import logging 
import asyncio 
import peewee_async 
import tornado.gen 
import tornado.httpclient 
from tornado.platform.asyncio import AsyncIOMainLoop 

AsyncIOMainLoop().install() 
app = tornado.web.Application(debug=True) 
app.listen(port=8888) 

# =========== 
# Defining Async model 
async_db = peewee_async.PooledPostgresqlDatabase(
    'reminderbot', 
    user='reminderbot', 
    password='reminderbot', 
    host='localhost' 
) 
app.objects = peewee_async.Manager(async_db) 
class AsyncHuman(peewee.Model): 
    first_name = peewee.CharField() 
    messenger_id = peewee.CharField() 
    class Meta: 
     database = async_db 
     db_table = 'chats_human' 


# ========== 
# Defining Sync model 
sync_db = peewee.PostgresqlDatabase(
    'reminderbot', 
    user='reminderbot', 
    password='reminderbot', 
    host='localhost' 
) 
class SyncHuman(peewee.Model): 
    first_name = peewee.CharField() 
    messenger_id = peewee.CharField() 
    class Meta: 
     database = sync_db 
     db_table = 'chats_human' 

# defining two handlers - async and sync 
class AsyncHandler(tornado.web.RequestHandler): 

    async def get(self): 
     """ 
     An asynchronous way to create an object and return its ID 
     """ 
     obj = await self.application.objects.create(
      AsyncHuman, messenger_id='12345') 
     self.write(
      {'id': obj.id, 
      'messenger_id': obj.messenger_id} 
     ) 


class SyncHandler(tornado.web.RequestHandler): 

    def get(self): 
     """ 
     An traditional synchronous way 
     """ 
     obj = SyncHuman.create(messenger_id='12345') 
     self.write({ 
      'id': obj.id, 
      'messenger_id': obj.messenger_id 
     }) 


app.add_handlers('', [ 
    (r"/receive_async", AsyncHandler), 
    (r"/receive_sync", SyncHandler), 
]) 

# Run loop 
loop = asyncio.get_event_loop() 
try: 
    loop.run_forever() 
except KeyboardInterrupt: 
    print(" server stopped") 

,这是我从Apache的基准获得:

ab -n 100 -c 100 http://127.0.0.1:8888/receive_async 

Connection Times (ms) 
       min mean[+/-sd] median max 
Connect:  2 4 1.5  5  7 
Processing: 621 1049 256.6 1054 1486 
Waiting:  621 1048 256.6 1053 1485 
Total:  628 1053 255.3 1058 1492 

Percentage of the requests served within a certain time (ms) 
    50% 1058 
    66% 1196 
    75% 1274 
    80% 1324 
    90% 1409 
    95% 1452 
    98% 1485 
    99% 1492 
100% 1492 (longest request) 




ab -n 100 -c 100 http://127.0.0.1:8888/receive_sync 
Connection Times (ms) 
       min mean[+/-sd] median max 
Connect:  2 5 1.9  5  8 
Processing:  8 476 277.7 479 1052 
Waiting:  7 476 277.7 478 1052 
Total:   15 481 276.2 483 1060 

Percentage of the requests served within a certain time (ms) 
    50% 483 
    66% 629 
    75% 714 
    80% 759 
    90% 853 
    95% 899 
    98% 1051 
    99% 1060 
100% 1060 (longest request) 

为什么是同步更快?我错过了什么瓶颈?

回答

6

在很长的解释:

http://techspot.zzzeek.org/2015/02/15/asynchronous-python-and-databases/

对于简短的说明:同步Python代码是简单且多标准库的插座模块中实现,这是纯C.异步Python代码比更复杂同步代码。每个请求都需要执行主要事件循环代码,这是用Python编写的(在asyncio的情况下),因此与C代码相比有很多开销。

像您这样的基准测试显示了异步的开销,因为您的应用程序和数据库之间没有网络延迟,而且您正在执行大量非常小的数据库操作。由于基准的每个其他方面都很快,因此事件循环逻辑的许多执行都会增加总运行时间的很大一部分。

Mike Bayer的观点与上面相关,这种低延迟的情况对于数据库应用程序来说是典型的,因此数据库操作不应该在事件循环中运行。

异步适用于高延迟的情况,例如Web应用程序和Web爬虫,其中应用程序花费大部分时间等待对等,而不是花费其大部分时间执行Python。结论:如果你的应用程序有一个很好的理由是异步(它处理慢对等体),为了一致的代码,拥有异步数据库驱动程序是一个好主意,但是需要一些开销。

如果您因其他原因不需要异步,请勿执行异步数据库调用,因为它们会慢一点。

+0

所以异步的Web框架像Sanic https://github.com/channelcat/sanic可以加快?它使用Python3.5 + uvloop – Pegasus

3

数据库ORM为异步体系结构引入了许多复杂性。 ORM中有几个地方可能发生阻塞,并且可能会压倒性地改变为异步形式。发生阻塞的地方也可能因数据库而异。我的猜测是为什么你的结果如此之慢,是因为有很多来自事件循环的非优化调用(我可能是严重错误的,我大多数时候使用SQLAlchemy或原始SQL)。根据我的经验,在线程中执行数据库代码通常更快,并在可用时产生结果。我无法为PeeWee说话,但SQLAlchemy非常适合在多线程中运行,并且没有太多缺陷(但存在的非常非常烦人)。

我建议你尝试使用ThreadPoolExecutor和同步Peewee模块并在一个线程中运行数据库函数。你将不得不修改你的主代码,但如果你问我,这将是值得的。例如,假设您选择使用回调代码,那么你的ORM查询可能是这样的:

from concurrent.futures import ThreadPoolExecutor 

executor = ThreadPoolExecutor(max_workers=10) 

def queryByName(name): 
    query = executor.submit(db_model.findOne, name=name) 
    query.add_done_callback(processResult) 

def processResult(query): 
    orm_obj = query.results() 
    # do stuff with the results 

你可以使用协程yeild fromawait,但它是一个有点问题对我来说。另外,我还不熟悉协程。只要开发人员注意死锁,数据库会话和事务,这段代码应该适用于Tornado。如果线程中出现问题,这些因素可能会减慢应用程序的速度。

如果您感觉非常冒险,MagicStack(asyncio后面的公司)有一个名为asyncpg的项目,它应该是非常快的!我一直在尝试的意思,但还没有找到时间:(

+0

我可以同意你的大部分答案,但是这句话:“MagicStack(asyncio背后的公司)”错误地引出了他们负责的想法或者asyncio的作者,他们对异步/等待做出了贡献,但这使得他们成为别人贡献者,系统中的另一部分 无论如何,我已经赞成你,因为你的例子很有用,并且可以帮助其他操作者在该舞台上进行研究。 – mydaemon