Instagram基于Python語言的Web Service效率提升之道
Instagram 目前部署了世界上最大規(guī)模的 Django Web 框架(該框架完全使用 Python 編寫)。我們最初選用 Python 是因為它久負盛名的簡潔性與實用性,這非常符合我們的哲學思想——“先做簡單的事情”。但簡潔性也會帶來效率方面的折衷。Instagram 的規(guī)模在過去兩年中已經(jīng)翻番,并且最近已突破 5 億用戶,所以急需最大程度地提升 web 服務效率以便我們的平臺能夠繼續(xù)順利地擴大。在過去的一年,我們已經(jīng)將效率計劃efficiency program提上日程,并在過去的六個月,我們已經(jīng)能夠做到無需向我們的 Django 層Django tiers添加新的容量來維持我們的用戶增長。我們將在本文分享一些由我們構建的工具以及如何使用它們來優(yōu)化我們的日常部署流程。
為何需要提升效率?
Instagram,正如所有的軟件,受限于像服務器和數(shù)據(jù)中心能源這樣的物理限制。鑒于這些限制,在我們的效率計劃中有兩個我們希望實現(xiàn)的主要目標:
- Instagram 應當能夠利用持續(xù)代碼發(fā)布正常地提供通信服務,防止因為自然災害、區(qū)域性網(wǎng)絡問題等造成某一個數(shù)據(jù)中心區(qū)丟失。
- Instagram 應當能夠自由地滾動發(fā)布新產(chǎn)品和新功能,不必因容量而受阻。
想要實現(xiàn)這些目標,我們意識到我們需要持續(xù)不斷地監(jiān)控我們的系統(tǒng)并與回歸regressions進行戰(zhàn)斗。
定義效率
Web services 的瓶頸通常在于每臺服務器上可用的 CPU 時間。在這種環(huán)境下,效率就意味著利用相同的 CPU 資源完成更多的任務,也就是說,每秒處理更多的用戶請求requests per second,RPS。當我們尋找優(yōu)化方法時,我們面臨的第一個最大的挑戰(zhàn)就是嘗試量化我們當前的效率。到目前為止,我們一直在使用“每次請求的平均 CPU 時間”來評估效率,但使用這種指標也有其固有限制:
- 設備多樣性。使用 CPU 時間來測量 CPU 資源并非理想方案,因為它同時受到 CPU 型號與 CPU 負載的影響。
- 請求影響數(shù)據(jù)。測量每次請求的 CPU 資源并非理想方案,因為在使用每次請求測量per-request measurement方案時,添加或移除輕量級或重量級的請求也會影響到效率指標。
相對于 CPU 時間來說,CPU 指令是一種更好的指標,因為對于相同的請求,它會報告相同的數(shù)字,不管 CPU 型號和 CPU 負載情況如何。我們選擇使用了一種叫做”每個活動用戶per active user“的指標,而不是將我們所有的數(shù)據(jù)關聯(lián)到每個用戶請求上。我們最終采用“每個活動用戶在高峰期間的 CPU 指令CPU instruction per active user during peak minute”來測量效率。我們建立好新的度量標準后,下一步就是通過對 Django 的分析來更多的了解一下我們的回歸。
Django web services 分析
通過分析我們的 Django web services,我們希望回答兩個主要問題:
- CPU 回歸會發(fā)生嗎?
- 是什么導致了 CPU 回歸發(fā)生以及我們該怎樣修復它?
想要回答第一個問題,我們需要追蹤“每個活動用戶的 CPU 指令CPU-instruction-per-active-user”指標。如果該指標增加,我們就知道已經(jīng)發(fā)生了一次 CPU 回歸。
我們?yōu)榇藰嫿ǖ墓ぞ呓凶?Dynostats。Dynostats 利用 Django 中間件以一定的速率采樣用戶請求,記錄關鍵的效率以及性能指標,例如 CPU 總指令數(shù)、端到端請求時延、花費在訪問內(nèi)存緩存(memcache)和數(shù)據(jù)庫服務的時間等。另一方面,每個請求都有很多可用于聚合的元數(shù)據(jù)metadata,例如端點名稱、HTTP 請求返回碼、服務該請求的服務器名稱以及請求中最新提交的哈希值hash。對于單個請求記錄來說,有兩個方面非常強大,因為我們可以在不同的維度上進行切割,那將幫助我們減少任何導致 CPU 回歸的原因。例如,我們可以根據(jù)它們的端點名稱聚合所有請求,正如下面的時間序列圖所示,從圖中可以清晰地看出在特定端點上是否發(fā)生了回歸。
CPU 指令對測量效率很重要——當然,它們也很難獲得。Python 并沒有支持直接訪問 CPU 硬件計數(shù)器(CPU 硬件計數(shù)器是指可編程 CPU 寄存器,用于測量性能指標,例如 CPU 指令)的公共庫。另一方面,Linux 內(nèi)核提供了 perf_event_open 系統(tǒng)調(diào)用。通過 Python ctypes 橋接技術能夠讓我們調(diào)用標準 C 庫的系統(tǒng)調(diào)用函數(shù)syscall,它也為我們提供了兼容 C 的數(shù)據(jù)類型,從而可以編程硬件計數(shù)器并從它們讀取數(shù)據(jù)。
使用 Dynostats,我們已經(jīng)可以找出 CPU 回歸,并探究 CPU 回歸發(fā)生的原因,例如哪個端點受到的影響最多,誰提交了真正會導致 CPU 回歸的變更等。然而,當開發(fā)者收到他們的變更已經(jīng)導致一次 CPU 回歸發(fā)生的通知時,他們通常難以找出問題所在。如果問題很明顯,那么回歸可能就不會一開始就被提交!
這就是為何我們需要一個 Python 分析器,(一旦 Dynostats 發(fā)現(xiàn)了它)從而使開發(fā)者能夠使用它找出回歸發(fā)生的根本原因。不同于白手起家,我們決定對一個現(xiàn)成的 Python 分析器 cProfile 做適當?shù)男薷摹Profile 模塊通常會提供一個統(tǒng)計集合來描述程序不同的部分執(zhí)行時間和執(zhí)行頻率。我們將 cProfile 的定時器timer替換成了一個從硬件計數(shù)器讀取的 CPU 指令計數(shù)器,以此取代對時間的測量。我們在采樣請求后產(chǎn)生數(shù)據(jù)并把數(shù)據(jù)發(fā)送到數(shù)據(jù)流水線。我們也會發(fā)送一些我們在 Dynostats 所擁有的類似元數(shù)據(jù),例如服務器名稱、集群、區(qū)域、端點名稱等。
在數(shù)據(jù)流水線的另一邊,我們創(chuàng)建了一個消費數(shù)據(jù)的尾隨者tailer。尾隨者的主要功能是解析 cProfile 的統(tǒng)計數(shù)據(jù)并創(chuàng)建能夠表示 Python 函數(shù)級別的 CPU 指令的實體。如此,我們能夠通過 Python 函數(shù)來聚合 CPU 指令,從而更加方便地找出是什么函數(shù)導致了 CPU 回歸。
監(jiān)控與警報機制
在 Instagram,我們每天部署 30-50 次后端服務。這些部署中的任何一個都能發(fā)生 CPU 回歸的問題。因為每次發(fā)生通常都包含至少一個差異diff,所以找出任何回歸是很容易的。我們的效率監(jiān)控機制包括在每次發(fā)布前后都會在 Dynostats 中掃描 CPU 指令,并且當變更超出某個閾值時發(fā)出警告。對于長期會發(fā)生 CPU 回歸的情況,我們也有一個探測器為負載最繁重的端點提供日常和每周的變更掃描。
部署新的變更并非觸發(fā)一次 CPU 回歸的唯一情況。在許多情況下,新的功能和新的代碼路徑都由全局環(huán)境變量global environment variables,GEV控制。 在一個計劃好的時間表上,給一部分用戶發(fā)布新功能是很常見事情。我們在 Dynostats 和 cProfile 統(tǒng)計數(shù)據(jù)中為每個請求添加了這個信息作為額外的元數(shù)據(jù)字段。按這些字段將請求分組可以找出由全局環(huán)境變量(GEV)改變導致的可能的 CPU 回歸問題。這讓我們能夠在它們對性能造成影響前就捕獲到 CPU 回歸。
接下來是什么?
Dynostats 和我們定制的 cProfile,以及我們建立的支持它們的監(jiān)控和警報機制能夠有效地找出大多數(shù)導致 CPU 回歸的元兇。這些進展已經(jīng)幫助我們恢復了超過 50% 的不必要的 CPU 回歸,否則我們就根本不會知道。
我們?nèi)匀贿€有一些可以提升的方面,并很容易將它們地加入到 Instagram 的日常部署流程中:
- CPU 指令指標應該要比其它指標如 CPU 時間更加穩(wěn)定,但我們?nèi)匀挥^察了讓我們頭疼的差異。保持“信噪比signal:noise ratio”合理地低是非常重要的,這樣開發(fā)者們就可以集中于真實的回歸上。這可以通過引入置信區(qū)間confidence intervals的概念來提升,并在信噪比過高時發(fā)出警報。針對不同的端點,變化的閾值也可以設置為不同值。
- 通過更改 GEV 來探測 CPU 回歸的一個限制就是我們要在 Dynostats 中手動啟用這些比較的日志輸出。當 GEV 的數(shù)量逐漸增加,開發(fā)了越來越多的功能,這就不便于擴展了。相反,我們能夠利用一個自動化框架來調(diào)度這些比較的日志輸出,并對所有的 GEV 進行遍歷,然后當檢查到回歸時就發(fā)出警告。
- cProfile 需要一些增強以便更好地處理封裝函數(shù)以及它們的子函數(shù)。
鑒于我們在為 Instagram 的 web service 構建效率框架中所投入的工作,所以我們對于將來使用 Python 繼續(xù)擴展我們的服務很有信心。我們也開始向 Python 語言本身投入更多,并且開始探索從 Python 2 轉移 Python 3 之道。我們將會繼續(xù)探索并做更多的實驗以繼續(xù)提升基礎設施與開發(fā)者效率,我們期待著很快能夠分享更多的經(jīng)驗。
本文作者 Min Ni 是 Instagram 的軟件工程師。































