Odoo 与 asyncio

Odoo升级到V11后原生支持运行于Python 3.x环境。于是好事者立即联想到了Python 3的异 步IO(asyncio)的火热特性。考虑Odoo是否能通过采用asyncio在面对大并发访问时提高后台数据库的访问效率。

看了SQLAlchemy作者: Mike Bayer的大作: Asynchronous Python and Database之后,可以负责任的得出结论了:非但不能用异步IO来提高 Odoo的性能,而且使用异步IO可能还会严重影响Odoo的性能。

这是怎么一回事呢?先来了解一点异步的基本知识。

什么是异步IO?

异步IO是实现程序并发运行的一种方式,它可以允许当IO操作的响应尚未返回时继续执行进 程。这需要IO函数的调用必须是非阻塞的(None-Blocking),这样函数调用在实际的IO 操作完成或甚至开始之前就立即返回了。另外,在一个无限循环中包含一个OS相关的轮询系统(比如 epoll), 用于查询IO操作的对象,即文件描述 符,当对应文件描述符有数据存在时,就对其执行IO操作,当完成后,轮询系统继续循环执 行下一个IO操作。

非阻塞IO作为一种处理多线程运行开销过高的替代方案,其典型的应用场景是监听处理大量包 含缓慢、休眠状态的TCP连接,这些连接只‘偶尔’发送数据的服务,比如“聊天服务器”,或其他消息系统。近年来异步IO的方法也运用到了HTTP相关的服务和应用,这使得其能为非常大量的HTTP连接请求提供服务,而不需要为每一个请求开启一个专属的线程。尤其是,当存在来自缓慢的HTTP客户端的请求时,并不会影响同时为巨量的其他请求提供响应服务。结合了这种非阻塞IO和long polling技术的web服务器,比如nginx玩的风生水起。

JS搅乱了一滩浑水

异步IO与消息循环是紧密结合的。对于JS这这种为客户端浏览器设计的脚本语言,用于随时响应浏览器中的各种消息。JS是典型的使用消息循环和回调(Call Back)方式的语言,相反的,它(直到到最近提出的Web Workers完全没有多线程的概念。而当nodejs出现后,大量熟悉这种异步IO编程的前端程序开发人员涌向后端,并到处布道,推销非阻塞IO的处理方式。这使得异步方式被无限扩大到了所有网络连接领域,其中也包括数据库连接。而数据在每个进程中只有少量的连接,一般是10-50,而且往往有连接池来减轻TCP启动开销。并且对于良好配置的本地网络数据库或数据库集群处理速度非常快,并且性能可控。这与非阻塞IO编程所要应对的场景是截然不同的。

Python的异步IO编程

在nodejs之前,学术界已经开始对多线程模式编程的批评。表示多线程面对成千上万的连接时其创建和维持的开销很大;另外,多线程编程困难,代码不确定性较大。同时因为Python世界里头疼的全局锁GIL问题的存在,使得Python程序员们也倾向于异步模式编程了。

回调(Call Back) 方式是异步IO编程的一种,但是这种方式很容易导致冗余,纠缠,难以维护的代码,这种现象被叫做“Callback spaghetti”。

Python很早认识到了回调方式的问题。所以发展出诸如eventlet, gevent这样的“隐式异步IO”库。以及在Python 3中引入标准库的asyncio,它与前者相反属于“显式异步IO”库。这两种方式都采用了coroutine技术以避免call back带来的代码混乱的问题。但是‘隐式异步IO’方式存在比如“猴子补丁”,有限的第三方库支持等诸多问题,所以asyncio作为标准库是更推荐的方式。

异步编程与数据库访问

Mike Bayer在文章里指出了一个大概绝大多数人的一个误解:

以数据库操作为主的Python应用程序在运行时对数据库的访问的时间开销是整个程序的性能瓶颈。这是错误的

对于JAVA或C这样的编译语言也许是正确的,但是Python的运行速度实在太慢了。如果只是标准的数 据库CRUD的操作(一般的Python应用程序大多也都是属于这类操作,Odoo也属于这样的应用,OLAP(在线实时分析)这样的操作不在讨论范围),数据库的运行速度要远高于Python的运行速度,并且在数据库的连接上我 们往往也会使用数据库连接池把创建数据库连接的时间开销降到最低。Mike用实际的测试数 据来支持他的观点。在测试中Mike也证实了多线程的模式要比异步模式对于数据库的并发访 问上要高效的多。异步编程不仅不能带来性能的提升,甚至会导致性能的下降。