Python web 應用性能調(diào)優(yōu)

為了快速上線啸箫,早期很多代碼基本是怎么方便怎么來耸彪,這樣就留下了很多隱患,性能也不是很理想忘苛,python 因為 GIL 的原因蝉娜,在性能上有天然劣勢,即使用了 gevent/eventlet 這種協(xié)程方案扎唾,也很容易因為耗時的 CPU 操作阻塞住整個進程召川。前陣子對基礎代碼做了些重構,效果顯著胸遇,記錄一些荧呐。

設定目標:

性能提高了,最直接的效果當然是能用更少的機器處理相同流量纸镊,目標是關閉 20% 的 stateless webserver.盡量在框架代碼上做改動倍阐,不動業(yè)務邏輯代碼。低風險 (歷史經(jīng)驗告訴我們薄腻,動態(tài)一時爽收捣,重構火葬場....)

治標

常見場景是大家開開心心做完一個 feature, sandbox 測試也沒啥問題庵楷,上線了罢艾,結(jié)果 server load 飆升,各種 timeout 都來了尽纽,要么 rollback 代碼咐蚯,要么加機器。問題代碼在哪?

我們監(jiān)控用的是 datadog (statsd協(xié)議)弄贿,對這種問題最有效的指標是看每個接口的 avg_latency * req_count 得到每個接口在一段時間內(nèi)的總耗時春锋,在柱狀圖上最長的那塊就是對性能影響最大的接口。進一步的調(diào)試就靠 cProfile 和讀代碼了差凹。

但很多時候出問題的代碼邏輯巨復雜期奔,還很多人改動過侧馅,開發(fā)和 sandbox 環(huán)境數(shù)據(jù)的量和線上差距太大,無法復現(xiàn)問題呐萌,在線上用 cProfile 只能測只讀接口(為了不寫壞用戶數(shù)據(jù))馁痴。

而且這種方式只能治標,調(diào)試個別慢的業(yè)務接口肺孤,目標里說了只想改框架罗晕,提高整體性能,怎么整?

治本

我希望能對運行時進程狀態(tài)打 snapshot赠堵,每次快照記錄下當前的函數(shù)調(diào)用棧小渊,疊合多次采樣,出現(xiàn)次數(shù)多的函數(shù)必然就是瓶頸所在. 這思想在其他語言里用的也很多茫叭,其實就是 Brendan Gregg 的 flamegraph.

以前內(nèi)部做過類似的事情酬屉,不過代碼是侵入式的,在運行時通過 signal, inspect, traceback 等模塊杂靶,定期打調(diào)用棧的 snapshot, 輸出到文件梆惯,轉(zhuǎn)成 svg 的 flamegraph 來看酱鸭,但是 overhead 太高吗垮,后來棄用了。

后來利用了 uber 開源的一個工具: https://github.com/uber/pyflame, 可以非侵入式得對運行中的 python 進程做 snapshot, 輸出成 svg.

效果如圖:

橫條越長的部分凹髓,表示被采樣到的次數(shù)越多烁登,從下往上可以看到在每一層上的函數(shù)耗時分布。

使用非常簡單:

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

-s 60蔚舀, 總采樣時間為 60s-r 0.01饵沧, 以0.01s 的頻率做采樣

在最終的輸出圖上可能有比較長的 IDLE 時間, pyflame 只能捕獲到當前獲取了 GIL 的代碼的調(diào)用錢,其他的部分就會是 IDLE, 包括幾種情況:

IO wait, 比如 call 一個很慢的 rpc server赌躺, client 等待過程中狼牺,采集到的時間就是 IDLEC 編寫的部分進程處于空閑時間。

大體可以認為 pyflame 上采樣到的部分是 CPU heavy的代碼礼患。

通過 pyflame, 可以很快得對進程運行時耗時分布有個大概的感覺是钥,即使你完全不了解業(yè)務邏輯.

重構

線上 web 應用,前面是基于 flask的 web 端和api server, 后面是幾組業(yè)務不同的 RPC server缅叠,兩者之間通過 msgpack 通信. 為了方便悄泥, RPC server 也是基于 flask 的,通過 pyflame 調(diào)試肤粱,發(fā)現(xiàn) flask 的 overhead 還是很高的弹囚,在 RPC 那層, 一些接口實際業(yè)務代碼的采樣次數(shù)领曼,只有總采樣的1/6左右 (并不能反應實際耗時分布)鸥鹉,其余都耗在了 flask 層蛮穿。

RPC server

RPC 層不處理web邏輯, flask完全用不到毁渗,可以干掉绪撵。有想過替換成 thrift/protobuf 這種二進制通行協(xié)議,傳輸?shù)臄?shù)據(jù)不帶 schema 信息祝蝠,效率能高不少音诈,但這樣勢必要大改接口,還要考慮之后schema改動绎狭,升級時候server 和 client 端的兼容性問題细溅。本著不動業(yè)務代碼和低風險的原則,還是保守的 http + msgpack.

對于 RPC server, 索性跳過 web 框架儡嘶,直接實現(xiàn) WSGI喇聊,參考 pep333 , 非常簡單,改完 rpc server入口代碼不到200行蹦狂,用 wrk 做下 helloworld 的 benchmark, 并發(fā)輕松變3倍.

RPC client

改完 rpc server 層誓篱,負載已經(jīng)有了顯著降低(20% 左右),還有個性價比很高的優(yōu)化是替換 rpc client. 之前用的是 requests, 說實話凯楔,個人對這種接口漂亮窜骄,使用方便的庫一直是持保留態(tài)度的,尤其是在這種性能敏感的場景摆屯,在 pyflame 的采樣圖上也能看到 requests 代碼里的耗時很長.

嘗試用 https://github.com/gwik/geventhttpclient 替換掉 requests. 簡單的 benchmark 腳本測試下來邻遏,完成相同的請求數(shù), geventhttpclient 只用了 requests 1/4 的時間 (gevent patch 過的情況下).

修改完 RPC client 的代碼虐骑,上線后卻傻眼了, server load 降得很明顯准验,可是latency 卻直接上升了 30% 多???

經(jīng)過排查,發(fā)現(xiàn)替換 client 過后廷没,內(nèi)網(wǎng)流量莫名增加了糊饱,拿兩臺機器做 A/B testing, 效果很明顯。開始懷疑是 geventhttpclient 的 connection pool 實現(xiàn)有問題颠黎,導致 tcp 連接沒有復用另锋。

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

對比了 requests 和 geventhttpclient 的兩臺機器,syn 包的數(shù)目并沒有太大差別盏缤。

但抓包過程中偶然發(fā)現(xiàn)砰蠢,geventhttpclient 在發(fā)送 http 請求的時候,header 和 body 竟然是用兩個 packet 發(fā)送的, requests 底層是用的標準庫的 httplib, 會將 header buffer 起來和 body 通過一個packet 發(fā)出去唉铜,所以每發(fā)一次請求台舱,geventhttpclient 會多發(fā)一個 ip + tcp header(40字節(jié)),怪不得流量變多了。

把這個問題修了下, 上線后 latency 立刻回復了正常竞惋。順手把改動推到了官方: https://github.com/gwik/geventhttpclient/pull/85

總結(jié)

經(jīng)過一輪修改柜去,最后關閉了30% 的 stateless server. 總共動到的代碼也就幾百行,業(yè)務開發(fā)無感知拆宛。應該說性價比很高嗓奢。

在復雜業(yè)務邏輯下,調(diào)試性能問題總是特別頭疼浑厚,單機的 benchmark QPS 數(shù)據(jù)也就估個天花板股耽,意義不大,關鍵還是要完善監(jiān)控和工具鏈钳幅,幫助快速定位問題物蝙。下一步打算上 opentracing, 完善分布式環(huán)境下的性能追蹤。

最后小編自己也是一個有著6年工作經(jīng)驗的工程師敢艰,關于python編程诬乞,自己有做材料的整合,一個完整的python編程學習路線钠导,學習資料和工具震嫉。想要這些資料的可以關注小編,加入python學習交流Q群735967233牡属。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末票堵,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子湃望,更是在濱河造成了極大的恐慌换衬,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件证芭,死亡現(xiàn)場離奇詭異,居然都是意外死亡担映,警方通過查閱死者的電腦和手機废士,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蝇完,“玉大人官硝,你說我怎么就攤上這事《掏桑” “怎么了氢架?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長朋魔。 經(jīng)常有香客問我岖研,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任孙援,我火速辦了婚禮害淤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘拓售。我一直安慰自己窥摄,他們只是感情好,可當我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布础淤。 她就那樣靜靜地躺著崭放,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鸽凶。 梳的紋絲不亂的頭發(fā)上莹菱,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天,我揣著相機與錄音吱瘩,去河邊找鬼道伟。 笑死,一個胖子當著我的面吹牛使碾,可吹牛的內(nèi)容都是我干的蜜徽。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼票摇,長吁一口氣:“原來是場噩夢啊……” “哼拘鞋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起矢门,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤盆色,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后祟剔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體隔躲,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年物延,在試婚紗的時候發(fā)現(xiàn)自己被綠了宣旱。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡叛薯,死狀恐怖浑吟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情耗溜,我是刑警寧澤组力,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站抖拴,受9級特大地震影響燎字,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一轩触、第九天 我趴在偏房一處隱蔽的房頂上張望寞酿。 院中可真熱鬧,春花似錦脱柱、人聲如沸伐弹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惨好。三九已至,卻和暖如春随闺,著一層夾襖步出監(jiān)牢的瞬間日川,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工矩乐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留龄句,地道東北人。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓散罕,卻偏偏與公主長得像分歇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子欧漱,可洞房花燭夜當晚...
    茶點故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容