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 开源的一个工具: https://github.com/uber/pyflame, 可以非侵入式得对运行中的 python 进程做 snapshot, 输出成 svg.

效果如图:

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

使用非常简单:

pyflame -s60-r0.01${pid} | flamegraph.pl > myprofile.svg

-s 60, 总采样时间为 60s-r 0.01, 以0.01s 的频率做采样

在最终的输出图上可能有比较长的 IDLE 时间, pyflame 只能捕获到当前获取了 GIL 的代码的调用钱,其他的部分就会是 IDLE, 包括几种情况:

IO wait, 比如 call 一个很慢的 rpc server, client 等待过程中,采集到的时间就是 IDLEC 编写的部分进程处于空闲时间。

大体可以认为 pyflame 上采样到的部分是 CPU heavy的代码。

通过 pyflame, 可以很快得对进程运行时耗时分布有个大概的感觉,即使你完全不了解业务逻辑.

重构

线上 web 应用,前面是基于 flask的 web 端和api server, 后面是几组业务不同的 RPC server,两者之间通过 msgpack 通信. 为了方便, RPC server 也是基于 flask 的,通过 pyflame 调试,发现 flask 的 overhead 还是很高的,在 RPC 那层, 一些接口实际业务代码的采样次数,只有总采样的1/6左右 (并不能反应实际耗时分布),其余都耗在了 flask 层。

RPC server

RPC 层不处理web逻辑, flask完全用不到,可以干掉。有想过替换成 thrift/protobuf 这种二进制通行协议,传输的数据不带 schema 信息,效率能高不少,但这样势必要大改接口,还要考虑之后schema改动,升级时候server 和 client 端的兼容性问题。本着不动业务代码和低风险的原则,还是保守的 http + msgpack.

对于 RPC server, 索性跳过 web 框架,直接实现 WSGI,参考 pep333 , 非常简单,改完 rpc server入口代码不到200行,用 wrk 做下 helloworld 的 benchmark, 并发轻松变3倍.

RPC client

改完 rpc server 层,负载已经有了显著降低(20% 左右),还有个性价比很高的优化是替换 rpc client. 之前用的是 requests, 说实话,个人对这种接口漂亮,使用方便的库一直是持保留态度的,尤其是在这种性能敏感的场景,在 pyflame 的采样图上也能看到 requests 代码里的耗时很长.

尝试用 https://github.com/gwik/geventhttpclient 替换掉 requests. 简单的 benchmark 脚本测试下来,完成相同的请求数, geventhttpclient 只用了 requests 1/4 的时间 (gevent patch 过的情况下).

修改完 RPC client 的代码,上线后却傻眼了, server load 降得很明显,可是latency 却直接上升了 30% 多???

经过排查,发现替换 client 过后,内网流量莫名增加了,拿两台机器做 A/B testing, 效果很明显。开始怀疑是 geventhttpclient 的 connection pool 实现有问题,导致 tcp 连接没有复用。

尝试用 tcpdump 抓 sync 包: tcpdump "tcp[tcpflags] & (tcp-syn) != 0"

对比了 requests 和 geventhttpclient 的两台机器,syn 包的数目并没有太大差别。

但抓包过程中偶然发现,geventhttpclient 在发送 http 请求的时候,header 和 body 竟然是用两个 packet 发送的, requests 底层是用的标准库的 httplib, 会将 header buffer 起来和 body 通过一个packet 发出去,所以每发一次请求,geventhttpclient 会多发一个 ip + tcp header(40字节),怪不得流量变多了。

把这个问题修了下, 上线后 latency 立刻回复了正常。顺手把改动推到了官方: https://github.com/gwik/geventhttpclient/pull/85

总结

经过一轮修改,最后关闭了30% 的 stateless server. 总共动到的代码也就几百行,业务开发无感知。应该说性价比很高。

在复杂业务逻辑下,调试性能问题总是特别头疼,单机的 benchmark QPS 数据也就估个天花板,意义不大,关键还是要完善监控和工具链,帮助快速定位问题。下一步打算上 opentracing, 完善分布式环境下的性能追踪。

最后小编自己也是一个有着6年工作经验的工程师,关于python编程,自己有做材料的整合,一个完整的python编程学习路线,学习资料和工具。想要这些资料的可以关注小编,加入python学习交流Q群735967233

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容