我在Uber運營大型分佈式系統這三年學到的經驗

Gergely在Uber公司內負責支付系統的運營。他在這篇文章裏分享了許多通用的經驗,對運營大型分佈式系統的方法給出了指導。

本文翻譯自“Operating a Large, Distributed System in a Reliable Way: Practices I Learned”,翻譯已獲得原作者Gergely Orosz授權。

在過去的幾年中,我一直在構建和運營大型分佈式系統:Uber的支付系統。在這段時間裏,我學到了許多分佈式架構的概念,也親眼看到了高負荷高可用系統的構建和運營是多麼富有挑戰性。就構建系統這件事來說,它本身是非常有趣的。要規劃好當流量增加10倍/100倍時系統該如何處理,即使硬件出故障數據也要能持久化保存等等,解決這些問題都可以讓人有極大的收穫。不過對於我個人來說,運營一套大型分佈式系統卻更是令人大開眼界的經歷

系統越大,墨菲定律“可能出錯的事終將出錯”就越適用。對於頻繁進行系統部署、許多開發者協同提交代碼、數據分散在多個數據中心、系統由全球海量用戶共同使用這樣的場景來說,就更明顯。在過去的幾年中,我經歷過許多次不同的系統故障,很多都大大出乎我意料之外。從可預見的問題——如硬件故障或不小心把有缺陷的代碼發佈到生產系統,到連接數據中心的網絡光纖被剷斷或多個級聯故障同時發生,許多次故障都讓部分系統無法正常工作,因而導致了停服,最終造成了極大的業務影響。

這篇文章是我在Uber工作期間,關於如何可靠地運營一套大型分佈式系統的經驗總結。我的經驗會很具有普遍性,運營類似規模系統的人也會有類似的經驗。我曾經與谷歌、Facebook、Netflix等公司的工程師們談過,他們的經驗和解決方案都很相似。不管系統是運行在自建數據中心(Uber大多數情況下是這樣的),還是運行在雲端(Uber的系統有時候會擴展到雲端),只要系統規模相似,文章中的想法和流程就會適用。不過如果要把經驗照搬到小型系統或非核心繫統時就請三思而後行,因爲很可能會過猶不及。

具體來說,我會談到以下話題:

  • 監控
  • 值班、異常檢測與告警
  • 停服與事件管理流程
  • 事後總結、事件回顧與持續改進的文化
  • 故障切換演習、有計劃的停機、容量規劃與黑盒測試
  • SLO、SLA及相應的報告
  • 獨立的SRE團隊
  • 對可靠性做持續投入
  • 深入閱讀建議

監控

要知道系統是否健康,我們就要回答問題:“我的系統在正常工作嗎?”要給出答案,首先就要收集關於系統關鍵部分的數據。對於運行在不同數據中心多臺服務器上,由多個不同服務組成的分佈式系統來說,決定哪些關鍵指標需要被監控,這事本來就不容易。

基礎設施健康監控:如果一或多臺服務器、虛擬機負載過高,那分佈式系統就會發生部分降級。服務要運行在服務器上,所以像CPU使用率、內存使用率之類的服務器基本健康信息就很值得監控。有些平臺就是專門做這樣的監控的,而且支持自動擴展。Uber有個很大的核心基礎設施團隊,專門爲大家提供底層的監控和告警。不管具體的實現方案如何,當服務的某些實例或基礎設施有問題時你都要被通知到,這些信息必須掌握。

服務健康監控:流量、錯誤、延遲。“後端服務健康嗎”?這個問題實在太泛了。觀察終端在處理的流量、錯誤率、終端延遲等,這些都對服務健康提供着有價值的信息。我喜歡爲它們各自設置專門的儀表盤。構建新服務時,使用合適的HTTP響應映射,並監控相應的編碼,這些都會提供很多關於系統狀態的信息。比如在客戶端出錯時返回4XX編碼,在服務端出錯時返回5XX編碼,這類監控很容易構建,也很容易解讀。

對系統延遲的監控值得多考慮一下。對於產品來說,目標就是讓大多數的終端用戶都有良好的體驗。但只監測平均延遲這項指標遠遠不夠,因爲平均延遲會掩蓋一小部分高延遲的請求。因此就經驗來說,監測P95、P99或P999(即95%、99%或99.9%的請求)的延遲指標會更好。監測這些指標得到的數字可以回答這樣的問題:99%的人發出的請求會有多快得到響應(P99)?1000個人發出請求,最慢能怎樣(P999)?如果有讀者對這個話題感興趣,可以進一步閱讀文章《latencies primer》。


上圖展示的是平均、P95和P99的延遲指標。請注意,儘管平均延遲是低於1秒的,但有1%的請求花費了兩秒或更多時間才完成,這樣的事實被平均延遲掩蓋了。

關於監控和可觀測性的話題可以繼續深入挖掘。有兩份材料值得細讀,一是谷歌的書《SRE:Google運維解密》,另外是分佈式系統監控的“四個黃金信號”。從中可以知道,如果你的面向用戶的系統只想監測四個指標的話,那麼關心流量、錯誤、延遲和飽和度就好了。喜歡吃快餐的話,可以讀讀Cindy Sridharan的電子書《分佈式系統可觀測性》,裏面講到了其它一些有用的工具,如與事件日誌、指標和跟蹤等有關的最佳實踐。

業務指標監控。監控服務可以讓我們瞭解服務的行爲看上去是否正確,但我們無法得知服務的行爲是否符合預期,使得“業務可以照常進行”。以支付系統爲例,一個關鍵問題就是:“人們用某種特別的支付方法買單,是不是已經足夠可以完成一次旅行?”辨別出服務使能的業務事件,並對這些業務事件進行監控,這是最重要的監控步驟之一。

我們的系統曾經經歷了若干次停服,我們深受其害,在發現這樣的停服事件沒有其它辦法可以監控之後,我們團隊構建了業務指標監控系統。當停服事件發生時,所有的服務都看起來一切正常,但某些跨服務的核心功能就是失效了。該監控是專門針對我們公司和業務領域而構建的。因此,我們就得花很多心思,以Uber的可觀測性技術棧爲基礎,努力地爲自己定製這類監控。

值班、異常檢測與告警

監控可以讓人們查看系統的當前狀態,是個非常有用的工具。但是,要在系統發生故障時自動檢測並且發出告警,讓人們可以採取相應地行動,監控只是第一步。

值班就是一個更廣的話題了:Increment雜誌在這方面做得很棒,對值班問題進行了許多方面的探討。我特別傾向於把值班做爲“誰構建,誰負責”思路的一個擴展。哪個團隊構建了服務,那就由哪個團隊負責值班。我們團隊就負責自己構建的支付服務的值班。因此不管什麼時候發生了告警,值班工程師都會響應並對問題進行處理。我們該如何從監控得出警告呢?

從監控數據中發現異常,這是一個巨大的挑戰,也是機器學習的用武之地。有許多第三方服務可以提供異常檢測功能。而且對於我們團隊來說很幸運,我們公司就有機器學習團隊可以爲我們提供支持,他們專門設計了適合Uber用例的解決方案。位於紐約的可觀測性團隊還就Uber如何做異常檢測這個主題寫過一篇很不錯的文章。從我們團隊的角度看,我們會把我們的監控數據推送到他們的管道中,並收到不同信任程度的告警,然後我們再決定要不要自動呼叫某位工程師。

什麼時候發出告警也是個很值得探討的問題。告警太少可能意味着錯過處理某些重大故障的時機,告警太多的話值班工程師就無覺可睡了,畢竟人的精力是有限的。因此對告警進行跟蹤並分類,並測量信噪比,這對調節告警系統非常重要。對告警信息進行檢查,並標記是否需要採取相應的動作,不斷減少不需要的告警,這就邁出了非常好的一步,讓可持續性的值班行爲處於良性循環了。


Uber內部使用的值班儀表盤例子,由位於維爾紐斯的Uber開發者體驗團隊構建

Uber位於維爾紐斯的開發者工具團隊開發了專門的值班工具,用於對告警進行註解,並對輪班進行可視化。我們團隊每週都會對上一週的值班情況進行回顧,分析痛點,並花時間提高值班體驗。這件事每週都會做。

停服與事件管理流程

設想一下,這周由你值班。半夜裏來了個電話告警,你就要調查一下是否發生了停服,是否會影響生產。結果看到系統真的有一部分失效了,謝天謝地,監控和告警的確反應了系統的真實情況。

對於小型系統來說,停服不算什麼大麻煩,值班工程師可以判斷出發生了什麼,以及爲什麼發生。一般來說他們都可以很快判斷問題,並進行解決。而對於使用了多種服務或微服務的複雜系統來說,很多工程師都在向生產提交着代碼,因此判斷出發生錯誤的根本原因,這就有相當大的難度了。在這種情況下,如果有些標準的處理流程,事情就會容易得多。

運行手冊附加到告警信息中,描述一下簡單的處理步驟,這就是第一層防線。如果團隊的手冊寫得非常好,那麼即使值班工程師對系統理解得並不深入,這一般也不會是什麼問題。手冊要不斷更新,當出現新的故障類型時要儘快進行修訂。

當服務由多個部門共同構建時,在部門之間溝通故障也很重要。在我們公司裏,幾千個工程師共同工作,他們會自己選擇自認爲合適的時機將服務部署到生產環境,因此事實上每個小時都會發生幾百次部署。對一個服務的部署可能會影響另一個看起來壓根不相關的服務。因此,是否有清晰標準的故障廣播和溝通渠道就成了故障順利解決的關鍵。曾經發生過好幾個案例,告警看起來與我之前見過的似乎一點關聯都沒有,幸好我想起來別的團隊中有些同事曾經見過類似的古怪問題。在一個大的故障處理羣中,我們一起很快就定位出了根本原因並解決掉了。做爲一個團隊來處理問題,總會比單個經驗豐富的員工快得多。

立刻恢復生產,事後調查原因。在處理故障的過程中,我也經常會“腎上腺素激增”,想要直接把問題當場解決掉。一般來說故障都是由某次新部署的代碼導致的,變更的代碼中會有某些很明顯的錯誤。在以前我一般不會回滾代碼,而是直接去修改代碼併發布新版本。但事實上一邊生產的故障沒有解決,另一邊卻去修改代碼中的缺陷,這實在不是個好主意,這麼做收益很小,損失卻很大。因爲新的修復代碼必須儘快完成,那就只能在生產環境進行測試,這麼做反而更加容易引入新的缺陷,甚至舊的故障還沒解決,新的問題又出現了。我也曾經見過類似原因導致的一連串的故障。因此請一定記住,要先恢復生產,抵抗住立刻修復或調查根本原因的誘惑。事實上第二天回來上班再仔細調查根本原因也未嘗不可。

事後總結、事件回顧與持續改進的文化

這裏講的是一個團隊如何進行故障的後續處理。他們會繼續做調查嗎?他們會不會停下手頭所有與生產有關的工作,花費驚人的代價來做一次系統級的修復?

事後總結是構建健壯系統的基石。好的事後總結是非常細緻和無可挑剔的。Uber的事後總結模板已經隨着時間的推移而演進好多個版本了,內容包括許多小節,有事件總結、大概影響、事件發生過程的關鍵時間點、根本原因分析、經驗總結及一系列的後續跟進內容。


一個與我在Uber使用的事後總結模板類似的例子

好的事後總結會對故障根本原因進行深入挖掘,得到改進措施來避免類似的故障發生,或者在下一次出現類似故障時可以快速檢測和恢復。我說的深入挖掘,不是說淺嘗輒止地知道“這次故障的原因是最近提交的代碼引入的缺陷,在代碼審查時沒能發現出來”就可以了,而是要用“五個爲什麼”的思考技巧去深入挖掘,最終得出一個更有意義的結論。比如下面的例子:

  • 爲什麼會發生這樣的問題?——> 缺陷是由某次提交的有缺陷的代碼導致的。
  • 爲什麼其他人沒能發現這個問題?——>代碼審查者沒注意到這樣修改代碼會導致這種問題。
  • 爲什麼我們完全依賴代碼審查者來發現這樣的問題?——>因爲我們沒有對這類用例的自動化測試。
  • 爲什麼這類用例沒做自動化測試?——>因爲沒有測試賬號的話,很難做測試。
  • 爲什麼我們沒有測試賬號?——>因爲現在的系統不支持。
  • 結論:現在我們知道了原來錯誤的根本原因在於沒有測試賬號,因此會建議讓系統支持添加測試賬號。做爲進一步的跟進措施,要爲將來所有類似的代碼改動編寫自動化測試用例。

事件回顧是事後總結的一個很重要的輔助工具。當許多團隊都對事後總結做了很細緻的工作時,其他團隊就會多了許多可以參考的內容並從中受益,並會設法完成一些預防性改進。一個團隊要讓別人覺得很可靠,他們提出的系統級改進措施要能得以推行,這一點很重要。

對於特別關注可靠性的公司來說,特別重大的事件回顧都會由有經驗的工程師們進行審查,並對其中的關鍵點提出意見。公司級的技術管理層也要對改進措施加以推進,尤其是對耗時長、可能阻礙其他工作進行的問題。健壯的系統不是一天就能構建出來的,肯定是經過持續改進慢慢迭代出來的。迭代來源於公司級的持續改進文化,要從事件中學到經驗。

故障切換演習、有計劃的停機、容量規劃與黑盒測試

有許多日常活動都需要巨大的投入,但要想保證大型分佈式系統的在線穩定運行,這樣的投入又是至關重要的。我加入Uber公司之後才第一次遇到了這樣的事情,因爲我就職的上一家公司不管從規模還是基礎設施來說,都不需要我們做這樣的事。

我一直認爲數據中心的故障切換演習是件非常沒意義的事,但親身經歷了幾次之後我的觀點慢慢發生了變化。我一直認爲,設計健壯的分佈式系統只要數據中心在失效時能快速恢復就好了。既然設計好了,理論上也行得通,那還爲什麼要不斷地定期嘗試呢?實際上答案與規模有關,而且也要測試在新的數據中心流量急劇增漲時,服務是否仍能高效地進行處理。

當切換髮生時,我觀察到的最常見的場景就是新數據中心的服務沒有足夠的資源來處理所有新涌入的流量。假設在兩個數據中心裏分別運行着ServiceA和ServiceB,每個數據中心運行着幾十上百個虛擬機,資源利用率是60%,告警的閾值是70%。現在我們做一次切換,把數據中心A的流量全都切到數據中心B去。在這種情況下假如不部署新的服務器,數據中心B肯定處理不了這些流量。部署新服務器可能會需要很長時間,因此請求很快就會開始堆積,並被拋棄。這樣的堵塞也會開始影響別的服務,導致其他系統的接連失效,而這些本來與這次切換並不相關。


數據中心故障切換失敗的可能示意圖

其它可能的失敗場景包括路由問題、網絡容量問題、後端壓力瓶頸等。所有可靠的分佈式系統都需要具備在不對用戶體驗造成任何影響的情況下進行數據中心切換的能力,因此也應該可以進行演習。在這裏我要強調“應該”,因爲這樣的演習是檢驗分佈式系統網絡可靠性最有效的方法之一。

有計劃的服務宕機演習是檢驗系統整體可靠性的非常棒的方法,也是發現隱藏依賴和對特定系統的不合理、非預期使用的好方法。對於面向用戶、已知依賴比較少的服務來說,做這樣的練習相對簡單。但關鍵核心系統要求高在線時間,或者被許多其他系統所依賴,做這樣的練習就沒那麼直觀了。但如果某天這個核心系統真的就失效了,又會怎樣呢?因此最好是用一次可控的演習來驗證答案,所有的團隊都要知道會有不可知的故障發生,並且準備就緒。

黑盒測試用於檢驗系統的正確性,與終端用戶的使用方式最爲接近。這種測試與端到端的測試很相近。除此之外,對於大多數產品來說恰當的黑盒測試可以確保他們的投入得到回報。關鍵的用戶流程和最常見的用戶界面測試用例,都是很好的選擇,可以讓黑盒測試得以進行:用一種在任何時間都可能被觸發的方式來做黑盒測試,檢驗系統是否可以正常運行。

以Uber爲例,一個很明顯的黑盒測試用例就是在城市的範圍內檢測乘車者與司機的匹配是否正常。即在某個特定的城市裏,乘車者通過Uber發起的請求是否能得到司機的響應,並完成一筆載客生意。當這個場景自動化之後,這個測試就可以在不同的城市定期模擬運行了。有了健壯的黑盒測試系統就可以很容易地驗證系統是否可以正常工作,起碼是部分系統。黑盒測試對於故障切換演習也很有用,是獲取故障切換狀態的最快方式。


在一次故障切換演習中進行黑盒測試的例子,故障確認後手動進行回滾。橫座標爲時間,縱座標爲運行失敗的用例數

對大型分佈式系統來說,容量規劃也差不多同等重要。我對大型分佈式系統的定義是指那些每個月要支出幾萬甚至幾十萬美元的計算與存儲系統。對於這樣規模的系統來說,相比構建在雲上的自動擴展方案,自建系統並進行固定數量的部署成本會更低些。但自建系統至少要考慮當峯值流量到來時的自動擴展問題,保證對業務流量進行正常平穩的處理。另一個問題是,下個月至少應該運行多少個實例呢?下個季度呢?下一年呢?

對於成熟並精心保存了歷史數據的系統來說,對將來的流量進行預測並非難事。另外一件重要的事就是預算,要挑選供應商並鎖住雲服務提供商的優惠折扣。如果你的服務支出很大,而你又忽略了容量規劃,那你就錯失了一個非常簡單的減少並控制支出的方法。

SLO、SLA及相應的報告

SLO意味着服務等級目標(Service Level Objective),是系統可用性的一個數字化目標。好的實踐是爲每個獨立的服務都定義服務等級目標SLO,比如容量目標、延遲、準確度、可用性等。這些SLO可以用於觸發告警。下面是一些服務等級目標的例子:

SLO指標 子類 服務的值
容量 最小吞吐量 500請求/秒
預期最大吞吐量 2500請求/秒
延遲 預期中值響應時間 50-90ms
預期p99響應時間 500-800ms
準確度 最大錯誤率 0.5%
可用性 承諾的正常運行時間 99.9%

業務級SLO或功能級SLO是對上面這些服務的抽象。它們會包括直接影響用戶或業務的指標。比如一個業務級目標可以這麼定義:希望99.99%的郵件可以在一分鐘之內確認發送成功。這個SLO也可以與服務級SLO相對應(比如支付系統和郵件接收系統的延遲),也有可能會用別的方式去進行測量。

服務等級協議(SLA,Service Level Agreement)是服務提供者與服務使用者之間更廣泛的約定。一般來說,一個SLA會由多個SLO組成。比如,“支付系統99.99%可用”可以做爲一個SLA,它可以繼續分解爲各個支撐系統的特定SLO。

定義了SLO之後,下一步就是對它們進行測量並報告。對SLA和SLO進行自動化的測量和報告,這是個複雜的目標,會與工程和業務團隊的優先級相沖突。工程團隊不會感興趣,因爲他們已經有了各種不同級別的監控,可以實時地檢測故障。業務團隊也不會感興趣,他們更希望把精力用於提交功能,而不是用在一個不會很快產生業務影響的複雜項目上。

這就談到了另一個話題:已經或即將運營大型分佈式系統的公司要爲系統的可靠性投入專門的團隊。我們談談站點可靠性工程團隊。

獨立的SRE團隊

站點可靠性這個詞大概在2003年出自谷歌,谷歌公司現在已經有了超過1500名SRE工程師。因爲運營生產環境的任務越來越複雜,越來越自動化,所以很快這就成了個全職工作。工程師們會慢慢地全職進行生產自動化:系統越關鍵,故障也就越多,這件事就會越早發生。

快速成長的技術公司一般會比較早成立SRE團隊,他們會有自己的演進路線。Uber的SRE團隊成立於2015年,任務是對系統複雜度進行持續管理。別的公司在成立專門的SRE團隊同時,也可能成立專門的基礎設施團隊。當一個公司的跨團隊可靠性工作需要佔用多個工程師的時間時,就可以考慮成立專門的團隊做這件事了

有了SRE團隊,他們就會從運營的角度考慮,讓運營大型分佈式系統的工作變得更輕鬆。SRE團隊可能會有標準的監控和告警工具。他們可能會購買或自己構建值班相關工具,可以爲值班的最佳實踐提供建議。他們會爲故障回顧提供幫助,會構建系統來讓大家更容易地檢測、恢復和預防故障。他們也會主導故障切換演習,推動黑盒測試,並參與容量規劃。他們會驅動選擇、定製和構建標準工具,來定義和測量SLO,並進行上報。

不同的公司需要SRE團隊解決不同的痛點,所以不同公司的SRE團隊之間也很可能並不相同。它們的名稱也經常會不同:可能會叫運營團隊、平臺工程團隊或基礎設施團隊。關於站點可靠性,谷歌免費發佈了兩本必讀書,想深入瞭解SRE的話可以讀一下。

把可靠性做爲持續投入

不論構建什麼產品,完成第一版只是個開始。在第一版之後,新功能會通過後面的迭代不斷加入。如果產品很成功,可以帶來商業上的回報,迭代工作就會不斷繼續。

分佈式系統也有類似的生命週期,而且需要不斷地進行投入。不只是開發新功能,還要滿足擴展的需求。當系統要承擔更大的負載、存儲更多數據、從事相關工作的工程師越來越多時,就需要持續關注,才能讓它一直平穩地運行。許多第一次構建分佈式系統的人都會把它想像成一輛車:投入使用後,只需要隔幾個月做一次定期保養就可以了。從系統運行的角度來說這個對比行不通。

我更願意把運營一個分佈式系統所要花費的功夫類比成運營一個大型組織,比如一家醫院。要讓一家醫院運營良好,就要不斷地做驗證和檢查(監控、告警、黑盒測試等)。新員工和新設備會不斷投入進來:對醫院來說就是醫生和護士等員工,以及新型醫療儀器之類的設備;對分佈式系統來說就是加入新員工,上線新服務。隨着人和服務的數量不斷增長,舊的做事方式開始變得不夠高效:可以想像,鄉鎮裏面的小診所和城市裏的大醫院運營方式肯定是不同的。因爲要用更高效的方式做事,所以產生了全職工作,對效率的測量和報告也變得很重要。因此大型醫院就會有更多的支撐型員工,比如財務、人力資源和安保;運營大型分佈式系統也要依靠基礎設施、SRE等支持團隊。

要讓一個團隊可以運營好一個可靠的分佈式系統,公司要對運營做持續投入,不只是這些系統本身,還有構建它們的平臺。

深入閱讀建議

儘管這篇文章的內容已經夠長了,但它仍然只談到了皮毛而已。要想深入理解如何運營分佈式系統,我推薦下面這些內容:

在線資源

點擊這裏查看Hacker News上對這篇貼子的留言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章