0x00 Why

为何做这件事,在去年的一个项目中,算法同学要使用在线模型训练,不得不使用 python 的 tf 框架,这样我们不得不是 python web 框架,当时因为团队里面没人懂 python 相关的知识,只是简单的用 tornado 搭建的一个。但是在后期使用过程中,压测发现了 tornado 在低耗时的接口不够稳定,波动比较大。我们的接口一般 20ms,但是经常波动到 40ms,并发其实也不大。所以经历了痛苦过后,为了满足后续类似的场景,我们需要打造一款内部使用的 python web 框架,能够支持低耗时接口的稳定性,以及稍微性能强点。

0x01 How

基于以上的出发点,开始调研 python web 相关性能比较高的框架,看到了 sanic。说是性能比较高。先看下 sanic 作者开发 sanic 的动机,是因为他看下了下面这篇文章:

uvloop: Blazing fast Python networking

是因为 python3.4 推出了 asyncio,解决令人诟病的异步 io 性能问题,3.5 后推出了 uvloop,基于 libuv,libuv 是一个使用 C 语言实现的高性能异步 I/O 库,uvloop 用来代替 asyncio 默认事件循环,可以进一步加快异步 I/O 操作的速度,而 tornado 在 python3 中还没有使用 uvloop。

首先来看下 uvloop 的性能表现:

在这里插入图片描述

0x02 tornado vs sanic

口说无凭,直接压测看数据,我们同时构造两个框架的一个接口,接口里面没有任务逻辑,就是简单的返回一个 helloworld,都是起一个进程,实测的数据

就框架本身而言,sanic 确实性能高。然后我又对各个耗时阶段的接口进行压测,来判断不同耗时,两个框架的表现。

Sanic

Sleep(ms)\Thread 1 10 50 100
1 1-3 之间波动,波动范围为 0%~300%,周期性的 1-6 之间波动,波动范围为 0%~600%,周期性的 1-11 之间波动,波动范围为 0%~1100%,周期性的 1-7 之间波动,波动范围为 0%~700%,周期性的
10 11-15 之间波动,波动范围为 10%~50%,周期性的 12-17 之间波动,波动范围为 20%~70%,周期性的 11-20 之间波动,波动范围为 10%~100%,周期性的 11-31 之间波动,波动范围为 10%~310%,周期性的
50 51-56,周期性的波动 54-60,周期性 54-71,周期性的 51-61,周期的
100 101-106 之间波动,波动范围为 1%~6%,周期性的 103-110 之间波动,波动范围为 3%~10%,周期性的 103-120 之间波动,波动范围为 3%~20%,周期性的 105-130 之间波动,波动范围为 5%~30%,周期性的

Tornado

Sleep(ms)\Thread 1 10 50 100
1 2-4 之间波动,波动范围为 200%~400%,周期性的 6-11 之间波动,波动范围为 600%~1100%,周期性的 21-56 之间波动,波动范围为 2100%~5600%,周期性的 50-106 之间波动,波动范围为 5000%~10600%,周期性的
10 11-15 之间波动,波动范围为 110%~150%,周期性的 11-18 之间波动,波动范围为 110%~180%,周期性的 28-43 之间波动,波动范围为 280%~430%,周期性的 62-104 之间波动,波动范围为 620%~1040%,周期性的
50 52-57 之间波动,周期性的 54-62,周期性的 51-80,周期性的 53-75,周期性的
100 101-108 之间波动,波动范围为 1%~8%,周期性的 101-111 之间波动,波动范围为 1%~11%,周期性的 101-111 之间波动,波动范围为 1%~11%,周期性的 101-140 之间波动,波动范围为 1%~40%,周期性的

result

sleep 1ms&100 个线程压的情况

Sanic 波动为 1-7ms,而 Tornado 已经最低到 50ms,最高飙到 100 多 ms,显然在 10ms 以内的接口,并发超过 50 的,不适合使用 Tornado,推荐使用 Sanic,Sanic 赢一局,Sanic1:0Tornado

sleep 10ms &100 个线程压的情况

Sanic 波动为 11-31ms,而 Tornado 已经最低到 62 了,最高到 100 多毫秒,显然在 100 毫秒内,并发超过 50 的时候,还是 Sanic 更胜一筹,Sanic 再赢一局,Sanic2:0Tornado

sleep 50ms&100 个线程压的情况

Sanic 波动为 51-61,Tornado 为 53-73,两个框架之间差别不大,建议使用 Sanic,Sanic3:0Tornado。

sleep 100ms & 100 个线程压的情况

Sanic 波动为 105 到 130,Tornado 波动为 101-140,选择谁都可以。这次打个平手Sanic2:0Tornado

综上所述,sanic 性能确实优于 tornado。所以新框架采用 sanic 来开发。

0x04 框架设计

基于以上的设计,调研了一下用到的库。

sanic

redis

redis-py:官方推荐,配合 hiredis-py 会得到性能的极致提升。

mysql

pymysqlpool:mysql 连接池,包装pymysql提供连接池的功能。
还可以使用 pymysql+dbutils自己编写连接池的代码。

config

配置文件支持 ini 格式的,保持跟 go 的一样。
ConfigParser

官方使用 py 的配置文件,采用该方式更方便。

logging

pipenv

click 构建命令行工具

利用click开发。一条命令就能新建一个新的项目,直接可运行。

框架性能

目前框架开发已经完成,内部已经发布了第一个版本,对该框架的性能也进行了一个压测。

sanic 空逻辑性能测试(只返回一个 json 串)

单进程处理能力比较强,建议优先单进程

sanic 加入 log 测试(打印一条 log 到文件,多进程不丢日志,启用进程安全的 log)

sanic 加入 log 测试(打印一条 log 到文件,多进程丢日志,不启用进程安全的 log)

sanic 加入 redis 性能测试

Redis 稳定性较好,且增加线程或者进程呈线性增长,直到到达瓶颈。且尝试增加服务能力的时候,最好是起和并发量一样的进程数,不然来回切进程反而会降低 qps。

压测结果分析

建议采用单进程,开启普通 log,无包装多进程安全的 log。
最后我们测试了下 4 个线程压一个进程,打印一条 log(普通 log),set 一次 redis 的业务,qps:3200

在不要求日志不丢的,耗时可以放宽的情况下,可以提升进程数来提升性能。

0x05

虽然 sanic 框架性能很好,但是 python 自身的性能,以及 python 库的一些影响,仍然不能达到 go 的性能。但是应付一些中小型项目是可以了。后面持续优化,有机会开源出来。


↙↙↙阅读原文可查看相关链接,并与作者交流