Python web 应用性能调优

2020-06-19 00:00:00 代码 接口 性能 耗时 采样
作者:徐叶佳@Glow
原文链接:Python web 应用性能调优

为了快速上线,早期很多代码基本是怎么方便怎么来,这样就留下了很多隐患,性能也不是很理想,python 因为 GIL 的原因,在性能上有天然劣势,即使用了 gevent/eventlet 这种协程方案,也很容易因为耗时的 CPU 操作阻塞住整个进程。前阵子对基础代码做了些重构,效果显著,记录一些。

设定目标:

  • 性能提高了,直接的效果当然是能用更少的机器处理相同流量,目标是关闭 20% 的 stateless webserver.
  • 尽量在框架代码上做改动,不动业务逻辑代码。
  • 低风险 (历史经验告诉我们,动态一时爽,重构火葬场....)

治标

常见场景是大家开开心心做完一个 feature, sandbox 测试也没啥问题,上线了,结果 server load 飙升,各种 timeout 都来了,要么 rollback 代码,要么加机器。问题代码在哪?

我们监控用的是 datadog (statsd协议),对这种问题有效的指标是看每个接口的 avg_latency * req_count 得到每个接口在一段时间内的总耗时,在柱状图上长的那块就是对性能影响大的接口。进一步的调试就靠 cProfile 和读代码了。

但很多时候出问题的代码逻辑巨复杂,还很多人改动过,开发和 sandbox 环境数据的量和线上差距太大,无法复现问题,在线上用 cProfile 只能测只读接口(为了不写坏用户数据)。

而且这种方式只能治标,调试个别慢的业务接口,目标里说了只想改框架,提高整体性能,怎么整?

治本

我希望能对运行时进程状态打 snapshot,每次快照记录下当前的函数调用栈,叠合多次采样,出现次数多的函数必然就是瓶颈所在. 这思想在其他语言里用的也很多,其实就是 Brendan Gregg 的 flamegraph.

以前内部做过类似的事情,不过代码是侵入式的,在运行时通过 signal, inspect, traceback 等模块,定期打调用栈的 snapshot, 输出到文件,转成 svg 的 flamegraph 来看,但是 overhead 太高,后来弃用了。

后来利用了 uber 开源的一个工具: github.com/uber/pyflame, 可以非侵入式得对运行中的 python 进程做 snapshot, 输出成 svg.

效果如图:


横条越长的部分,表示被采样到的次数越多,从下往上可以看到在每一层上的函数耗时分布。

使用非常简单:

pyflame -s 60 -r .01 ${pid} | flamegraph.pl > myprofile.svg

相关文章