本文最先发表于我的博客:Pylixm'Wikihtml
应项目的需求,咱们使用tornado开发了一个api系统,系统开发完后,在8核16G的虚机上通过压测qps只有200+。与咱们当初定的QPS 大于2k差了一个数量级,因而便开始了漫长的优化之路。在优化过程当中,学了许多东西,有必要整理记录下备查。python
咱们的技术选型:mysql
当初技术选型的时候选择tornado,即是由于其优秀的性能,这么低的QPS天然是不甘心。究竟tornado能够达到多少QPS呢?因而编写了简单的hello world,在上边的虚拟机中起16个进程下,使用ab压测QPS居然达到了惊人的6K,平均响应时间在毫秒级。这下有信心将api的QPS继续优化了。web
提高QPS, 可从两方面入手,一个是增长并发数,其二是减小平均响应时间。从目前状况看,增长进程并发数是最直接的手段,但当达到机器资源的瓶颈时,可靠堆叠机器来解决。那么
相比较下,减少平均响应更为重要。初步分析了咱们开发的api,平均响应时间在几百毫秒级别。大部分的时间花在系统与数据库的交互上,到这,便有了一个优化的主题思路:最大限度的下降平均响应时间。sql
咱们API完成的功能为,接受请求参数作一些列的认证判断(与数据库交互),将消息以广播的形式发送到rabbitmq供消费者消费,最后返回给客户端发送结果。根据此逻辑,影响响应时间的地方,分析以下:数据库
根据上边的问题,从如下几个方面入手:api
开发api时,由于对tornado 的异步特性不是很熟悉,便没有使用。后来随着测试的深刻,发现须要使用后,开始了解。
随着了解的深刻,发现tornado是并无很好的支持数据库的异步特性,更可能是对网络的异步,官网上也是写的”网络非阻塞框架“。
查阅官方文档,tornado的异步实现,见官方文档
总的来讲,使程序异步的方式有3种,参考这里。以下:websocket
第一种,使用tornado 的 gen.coruntine。网络
使用此种方式,须要异步数据库的驱动库,经查找现阶段并无很好的成熟的支持异步查询mysql的python驱动,放弃此种方案。
第二种,使用tornado 的线程模块。并发
此种方式比较方便,只须要在耗时的函数上添加装饰器便可,简单方便,能够说是一种万能方案,但此方案耗费系统资源。 系统资源并非咱们的瓶颈,咱们最后采纳了此种方式。
第三种,使用外部队列,单独其worker 进程或线程去处理。例如,celery 等。
此种方式增长了外部的依赖,增长了系统的复杂性和后期的维护难度,放弃此种方案。
增长了异步特性外有显著的提高。
数据库方便,咱们适用的是SQLAlchemy。使用ORM时,在减小裸sql带来的查询复杂度的同时,必然会增长查询数据库的耗时。咱们也作过测试,
使用pymsql连接mysql,直接使用裸sql查询与使用sqlalcemy 的对象查询的耗时差异有七、8个毫秒的时差,与sqlalchemy的裸sql方式执行时间几乎一致。
可见,sqlalchemy的orm方式是有必定时间耗损的。stackoverflow的一个问题,也验证了个人想法,见Why is loading SQLAlchemy objects via the ORM 5-8x slower than rows via a raw MySQLdb cursor?
针对数据库方面,咱们作了以下优化:
rabbitmq 方面,使用的是pika 做为驱动库链接的,使用方式是每次发送数据的时候建立连接和通道,发送完毕后当即关闭连接。考虑到是否可使用长连接,建立连接后不关闭,只关闭channel。修改后发现报错,具体代码以下:
# -*- coding:utf-8 -*- import pika from settings import settings class Client(object): def __init__(self, host, port, username, pwd): self.host = host self.port = port self.username = username self.pwd = pwd self.init_connection() def init_connection(self): user_pwd = pika.PlainCredentials(self.username, self.pwd) self.connection = pika.BlockingConnection(pika.ConnectionParameters( host=self.host, port=self.port, credentials=user_pwd)) # ...
补充错误材料分析 - todo
翻阅了pika的文档,发现其有异步的使用方式,且有与tornando 框架的结合的实例,见文档。
pika的异步方式,使用了和tornado 相同的基于epull的事件循环模型,如何将其与tornado 的IOloop结合是个问题,
其有个tornado的连接适配器,翻看其代码仍是有些不太明确如何使用,有时间的时候再继续研究下。
针对rabbitmq的优化咱们放弃了,但优化过程当中有些值得分析的文章,整理以下:
代码逻辑方便的优化,以下:
通过以上的优化,咱们的api 的 QPS 提高到了1200+, 因为时间问题,咱们暂停了继续的优化。经过本次QPS的优化过程,有几点感悟: