三年多前,我在騰訊負責的活動運營系統,由於業務流量規模的數倍增加,系統出現了各類各樣的異常,當時,做爲開發的我,7*24小時地沒日沒夜處理告警,週末和凌晨也常常上線,疲於奔命。後來,當時的老領導對我說:你不能總扮演一個「救火隊長」的角色, 要嘗試從系統總體層面思考產生問題的根本緣由,而後推動解決。
我幡然醒悟,「火」是永遠救不完的,讓系統可以自動」滅火」,纔是解決問題的正確方向。簡而言之,系統的異常不能老是依賴於「人」去恢復,讓系統自己具有「容錯」能力,纔是根本解決之道。三年多過去了,我仍然負責着這個系統,而它也已經從一個日請求百萬級的小Web系統,逐步成長爲一個高峯日請求達到8億規模的平臺級系統,走過一段使人難忘的技術歷程。
容錯實際上是系統健壯性的重要指標之一,而本文會主要聚焦於「容錯」能力的實踐,但願對作技術的同窗有所啓發和幫助。
(備註:QQ會員活動運營平臺,後面統一簡稱AMS)
1、 重試機制
最容易也最簡單被人想到的容錯方式,固然就是「失敗重試」,總而言之,簡單粗暴!簡單是指它的實現一般很簡單,粗暴則是指使用不當,極可能會帶來系統「雪崩」的風險,由於重試意味着對後端服務的雙倍請求。1. 簡單重試
咱們請求一個服務,若是服務請求失敗,則重試一次。假設,這個服務在常規狀態下是99.9%的成功率,由於某一次波動性的異常,成功率下跌到95%,那麼若是有重試機制,那麼成功率大概還能保持在99.75%。而簡單重試的缺陷也很明顯,若是服務真的出問題,極可能帶來雙倍流量,衝擊服務系統,有可能直接將服務沖垮。而在實際的真實業務場景,每每更嚴重,一個功能不可用,每每更容易引發用戶的「反覆點擊」,反而製造更大規模的流量衝擊。比起服務的成功率比較低,系統直接被衝擊到「掛掉」的後果明顯更嚴重。
簡單重試,要使用在恰當的場景。或者,主動計算服務成功率,成功率太低,就直接不作重試行爲,避免帶來太高的流量衝擊。
2. 主備服務自動切換
既然單一服務的重試,可能會給該帶來雙倍的流量衝擊,而最終致使更嚴重的後果,那麼咱們不如將場景變爲主備服務的自動重試或者切換。例如,咱們搭建了兩套獲取openid的服務,若是服務A獲取失敗,則嘗試從服務B中獲取。由於重試的請求壓力是壓到了服務B上,服務A一般不會由於重試而產生雙倍的流量衝擊。
這種重試的機制,看似比較可用,而實際上也存在一些問題:
(1) 一般會存在「資源浪費」的問題。由於備份服務系統,極可能長期處於閒置狀態,只有在主服務異常的時候,它的資源纔會被比較充分地使用。不過,若是對於核心的服務業務(例如核心數據、營收相關)進行相似的部署,雖然會增長一些機器成本和預算,但這個付出一般也是物有所值的。
(2) 觸發重試機制,對於用戶的請求來講,耗時必然增長。主服務請求失敗,而後再到備份服務請求,這個環節的請求耗時就至少翻倍增加,假設主服務出現鏈接(connect)超時,那麼耗時就更是大幅度增長。一個服務在正常狀態下,獲取數據也許只要50ms,而服務的超時時間一般會設置到500-1000ms,甚至更多,一旦出現超時重試的場景,請求耗時必然大幅度增加,極可能會比較嚴重地影響用戶體驗。
(3) 主備服務一塊兒陷入異常。若是是由於流量過大問題致使主服務異常,那麼備份服務極可能也會承受不住這種級別的流量而掛掉。
重試的容錯機制,在AMS上有使用,可是相對比較少,由於咱們認爲主備服務,仍是不足夠可靠。2、 動態剔除或者恢復異常機器
在AMS裏,咱們的後端涉及數以百計的各種服務,來支撐整個運營系統的正常運做。全部後端服務或者存儲,首先是部署爲無狀態的方式提供服務(一個服務一般不少臺機器),而後,經過公司內的一個公共的智能路由服務L5,歸入到AMS中。
(1) 全部服務與存儲,無狀態路由。這樣作的目的,主要是爲了不單點風險,就是避免某個服務節點掛了,致使整個服務就癱瘓了。實際上,即便像一些具備主備性質(主機器掛了,支持切換到備份機器)的接入服務,也是不夠可靠的,畢竟只有2臺,它們都掛了的狀況,仍是可能發生的。咱們後端的服務,一般都以一組機器的形式提供服務,彼此之間沒有狀態關係,支撐隨機分配請求。
(2) 支持平行擴容。遇到大流量場景,支持加機器擴容。
(3) 自動剔除異常機器。在咱們的路由服務,發現某個服務的機器異常的時候(成功率低於50%),就會自動剔除該機器,後續,會發出試探性的請求,確認等它恢復正常以後,再從新加回到服務機器組。
例如,假如一組服務下擁有服務機器四臺(ABCD),假設A機器的服務由於某種未知緣由,徹底不可用了,這個時候L5服務會主動將A機器自動從服務組裏剔除,只保留BCD三臺機器對外提供服務。而在後續,假如A機器從異常中恢復了,那麼L5再主動將機器A加回來,最後,又變成ABCD四臺機器對外提供服務。
在過去的3年裏,咱們逐步將AMS內的服務,漸漸從寫死IP列表或者主備狀態的服務,所有升級和優化爲L5模式的服務,慢慢實現了AMS後端服務的自我容錯能力。至少,咱們已經比較少遇到,再由於某一臺機器的軟件或者硬件故障,而不得不人工介入處理的狀況。咱們也慢慢地從疲於奔命地處理告警的苦難中,被解放出來。
3、 超時時間
1. 爲服務和存儲設置合理的超時時間
調用任何一個服務或者存儲,一個合理的超時時間(超時時間,就是咱們請求一個服務時,等待的最長時間),是很是重要的,而這一點每每比較容易被忽視。一般Web系統和後端服務的通訊方式,是同步等待的模式。這種模式,它會帶來的問題比較多。
對於服務端,影響比較大的一個問題,就是它會嚴重影響系統吞吐率。假設,咱們一個服務的機器上,啓用了100個處理請求的worker,worker的超時時間設置爲5秒,1個worker處理1個任務的平均處理耗時是100ms。那麼1個work在5秒鐘的時間裏,可以處理50個用戶請求,然而,一旦網絡或者服務偶爾異常,響應超時,那麼在本次處理的後續整整5秒裏,它僅僅處理了1個等待超時的失敗任務。一旦比較大機率出現這類型的超時異常,系統的吞吐率就會大面積降低,有可能耗盡全部的worker(資源被佔據,所有在等待狀態,直到5s超時才釋放),最終致使新的請求無worker可用,只能陷入異常狀態。
算上網絡通訊和其餘環節的耗時,用戶就等待了超過5s時間,最後卻得到一個異常的結果,用戶的心情一般是崩潰的。
解決這個問題的方式,就是設置一個合理的超時時間。例如,回到上面的的例子,平均處理耗時是100ms,那麼咱們不如將超時時間從5s下調到500ms。從直觀上看,它就解決了吞吐率降低和用戶等待過長的問題。然而,這樣作自己又比較容易帶來新的問題,就是會引發服務的成功率降低。由於平均耗時是100ms,可是,部分業務請求自己耗時比較長,耗時超過500ms也比較多。例如,某個請求服務端耗時600ms才處理完畢,而後這個時候,客戶端認爲等待超過500ms,已經斷開了鏈接。處理耗時比較長的這類型業務請求會受到比較明顯的影響。
2. 超時時間設置太短帶來的成功率降低
超時時間設置太短,會將不少原本處理成功的請求,當作服務超時處理掉,進而引發服務成功率降低。將所有業務服務,以一刀切的方式設置一個超時時間,是比較不可取的。優化的方法,咱們分爲兩個方向。(1) 快慢分離
根據實際的業務維度,區分對待地給各個業務服務配置不一樣的超時時間,同時,最好也將它們的部署服務也分離出來。例如,每天酷跑的查詢服務耗時一般爲100ms,那麼超時時間咱們就設置爲1s,某新手遊的查詢服務一般耗時爲700ms,那麼咱們就設置爲5s。這樣的話,總體系統的成功率,就不會受到比較大的影響。
(2) 解決同步阻塞等待
「快慢分離」能夠改善系統的同步等待問題,可是,對於某些耗時原本就比較長的服務而言,系統的進程/線程資源仍然在同步等待過程當中,沒法響應其餘新的請求,只能阻塞等待,它的資源仍然是被佔據,系統的總體吞吐率仍然被大幅度拉低。
解決的思路,固然是利用I/O多路複用,經過異步回調的方式,解決同步等待過程當中的資源浪費。AMS的一些核心服務,採用的就是「協程」(又叫「微線程」,簡單的說,常規異步程序代碼裏嵌套比較多層的函數回調,編寫複雜。而協程則提供了一種相似寫同步代碼的方式,來寫異步回調程序),以解決同步等待的問題。異步處理的簡單描述,就是當進程遇到I/O網絡阻塞時,就保留現場,馬上切換去處理下一個業務請求,進程不會由於某個網絡等待而中止處理業務,進而,系統吞吐率即便遇到網絡等待時間過長的場景,一般都能保持在比較高的水平。
值得補充一點的是,異步處理只是解決系統的吞吐率問題,對於用戶的體驗問題,並不會有改善,用戶須要等待的時間並不會減小。
3. 防重入,防止重複發貨
前面咱們提到,咱們設置了一個比較「合理的超時時間」,簡而言之,就是一個比較短的超時時間。而在數據寫入的場景,會引發新的問題,就咱們的AMS系統而言,就是發貨場景。若是是發貨請求超時,這個時候,咱們須要思考的問題就比較多了。
(1) 發貨等待超時,發貨服務執行發貨失敗。這種場景,問題不大,後續用戶從新點擊領取按鈕,就能夠觸發下一次從新發貨。
(2) 發貨等待超時,發貨服務實際在更晚的時候執行發貨成功,咱們稱之爲「超時成功」。比較麻煩的場景,則是每次都是發貨超時,而實際上都發貨成功,若是系統設計不當,有可能致使用戶能夠無限領取禮包,最終形成活動運營事故。
第二種場景,給咱們帶來了比較麻煩的問題,若是處理不當,用戶再次點擊,就觸發第屢次「額外」發貨。
例如,咱們假設某個發貨服務超時時間設置爲6s,用戶點擊按鈕,咱們的AMS收到請求後,請求發貨服務發貨,等待6s後,無響應,咱們給用戶提示「領取失敗」,而實際上發貨服務卻在第8秒執行發貨成功,禮包到了用戶的帳戶上。而用戶看見「領取失敗」,則又再次點擊按鈕,最終致使「額外」多發一個禮包給到這個用戶。
例子的時序和流程圖大體以下:
這裏就提到了防重入,簡單的說,就是如何確認無論用戶點擊多少次這個領取按鈕,咱們都確保結果只有一種預期結果,就是隻會給用戶發一次禮包,而不引發重複發貨。咱們的AMS活動運營平臺一年上線的活動超過4000個,涉及數以萬計的各類類型、不一樣業務系統的禮包發貨,業務通訊場景比較複雜。針對不一樣的業務場景,咱們作了不一樣的解決方案:
(1) 業務層面限制,設置禮包單用戶限量。在發貨服務器的源頭,設置好一個用戶僅能最多得到1個禮包,直接避免重複發放。可是,這種業務限制,並不是每一個業務場景都通用的,只限於內部具有該限制能力的業務發貨系統,而且,有一些禮包自己就能夠屢次領取的,就不適用了。
(2) 訂單號機制。用戶的每一次符合資格的發貨請求,都生成一個訂單號與之對應,經過它來確保1個訂單號,只發貨1次。這個方案雖然比較完善,可是,它是依賴於發貨服務方配合作「訂單號發貨狀態更新「的,而咱們的發貨業務方衆多,並不是每個都能支持」訂單號更新「的場景。
(3) 自動重試的異步發貨模式。用戶點擊領取禮包按鈕後,Web端直接返回成功,而且提示禮包在30分鐘內到帳。對於後臺,則將該發貨錄入到發貨隊列或者存儲中,等待發貨服務異步發貨。由於是異步處理,能夠屢次執行發貨重試操做,直到發貨成功爲止。同時,異步發貨是能夠設置一個比較長的超時等待時間,一般不會出現「超時成功」的場景,而且對於前端響應來講,不須要等待後臺發貨狀態的返回。可是,這種模式,會給用戶帶來比較很差的體驗,就是沒有實時反饋,沒法馬上告訴用戶,禮包是否到帳。
4. 非訂單號的特殊防刷機制
某些特殊的合做場景,咱們沒法使用雙方約定訂單號方式,例如一個徹底隔離獨立的外部發貨接口,不能和咱們作訂單號的約定。基於這種場景,咱們AMS專門作了一種防刷的機制,就是經過限制read超時的次數。可是,這種方案並不是完美解決重複發貨問題,只是能起到夠儘量減小避免被刷的做用。一次網絡通訊,一般包含:創建鏈接(connect),寫入數據發包(write),等待而且讀取回包(read),斷開鏈接(close)。
一般一個發貨服務若是出現異常,大多數狀況,在connect步驟就是失敗或者超時,而若是一個請求走到等待回包(read)時超時,那麼發貨服務另一邊就有可能發生了「超時但發貨成功」的場景。這個時候,咱們將read超時的發生次數記錄起來,而後提供了一個配置限制次數的能力。假如設置爲2次,那麼當一個用戶第一次領取禮包,遇到read超時,咱們就容許它重試,當還遇到第二次read超時,就達到咱們以前設置的閥值2,咱們就認爲它可能發貨成功,拒絕用戶的第三次領取請求。
這種作法,假設發貨服務真的出現不少超時成功,那麼用戶也最多隻能刷到2次禮包(次數可配置),而避免發生禮包無限制被刷的場景。可是,這種方案並不徹底可靠,謹慎使用。
在發貨場景,還會涉及分佈式場景下的CAP(一致性、可用性、分區容錯性)問題,不過,咱們的系統並不是是一個電商服務,大部分的發貨並無強烈的一致性要求。所以,整體而言,咱們是弱化了一致性問題(核心服務,經過異步重試的方式,達到最終一致性),以追求可用性和分區容錯性的保證。4、 服務降級,自動屏蔽非核心分支異常
對於一次禮包領取請求,在咱們的後端CGI會通過10多個環節和服務的邏輯判斷,包括禮包配置讀取、禮包限量檢查、登錄態校驗、安全保護等等。而這些服務中,就有不能夠跳過的核心環節,例如讀取禮包配置的服務,也有非核心環節,例如數據上報。對於非核心環節,咱們的作法,就是設置一個比較低的超時時間。
例如咱們其中一個統計上報服務,平均耗時是3ms,那麼咱們就將超時時間設置爲20ms,一旦超時則旁路掉,繼續按照正常邏輯走業務流程。
5、 服務解耦、物理隔離
雖然,你們都知道一個服務的設計,要儘量小和分離部署,如此,服務之間的耦合會比較小,一旦某個模塊出問題,受到影響的模塊就比較少,容錯能力就會更強。但是,從設計之初,就將每個服務有序的切割地很小,這個須要設計者具有超前的意識,可以提早意識到業務和系統的發展形態,而實際上,業務的發展每每是比較難以預知的,由於業務的形態會隨着產品的策略的改變而變化。在業務早期流量比較小的時候,一般也沒有足夠的人力和資源,將服務細細的切分。AMS從日請求百萬級的Web系統,逐漸成長爲億級,在這個過程當中,流量規模增加了100倍,咱們經歷了很多服務耦合帶來的陣痛。
1. 服務分離,大服務變成多個小服務
咱們經常說,雞蛋不能都放在一個籃子裏。AMS之前是一個比較小的系統(日請求百萬級,在騰訊公司內徹底是一個不起眼的小Web系統),所以,不少服務和存儲在早起都是部署在一塊兒的,查詢和發貨服務都放在一塊兒,無論哪個出問題,都相互影響。後來,咱們逐漸的將這些核心的服務和存儲,慢慢地分離出來,細細切分和從新部署。在數據存儲方面,咱們將原來3-5個存儲的服務,慢慢地切爲20多個獨立部署的存儲。
例如,2015年下半年,咱們就將其中一個核心的存儲數據,從1個分離爲3個。
這樣作帶來了不少好處:
(1) 原來主存儲的壓力被分流。
(2) 穩定性更高,再也不是其中一個出問題,影響整個大的模塊。
(3) 存儲之間是彼此物理隔離的,即便服務器硬件故障,也不會相互影響。2. 輕重分離,物理隔離
另一方面,咱們對於一些核心的業務,進行「輕重分離」。例如,咱們支持2016年「手Q春節紅包」活動項目的服務集羣。就將負責信息查詢和紅包禮包發貨的集羣分別獨立部署,信息查詢的服務相對沒有那麼重要,業務流程比較輕量級,而紅包禮包發貨則屬於很是核心的業務,業務流程比較重。
輕重分離的這個部署方式,能夠給咱們帶來一些好處:
(1) 查詢集羣即便出問題,也不會影響發貨集羣,保證用戶核心功能正常。
(2) 兩邊的機器和部署的服務基本一致,在緊急的狀況下,兩邊的集羣能夠相互支援和切換,起到容災的效果。
(3) 每一個集羣裏的機器,都是跨機房部署,例如,服務器都是分佈在ABC三個機房,假設B機房整個網絡故障了,反向代理服務會將沒法接受服務的B機房機器剔除,而後,剩下AC機房的服務器仍然能夠正常爲外界提供服務。
6、 業務層面的容錯
若是系統架構設計層面的「容錯」咱們都搭建完善了,那麼再繼續下一層容錯,就須要根據實際的業務來進行,由於,不一樣的業務擁有不一樣的業務邏輯特性,也可以致使業務層面的各類問題。而在業務層面的容錯,簡而言之,避免「人的失誤」。無論一我的作事性格多麼謹慎細心,也總有「手抖」的時候,在不經意間產生「失誤」。AMS是一個活動運營平臺,一個月會上線400多個活動,涉及數以千計的活動配置信息(包括禮包、規則、活動參與邏輯等等)。在咱們的業務場景下,由於種種緣由而致使「人的失誤」並很多。
例如,某個運營同窗看錯禮包發放的日限量,將本來只容許1天放量100個禮包的資源,錯誤地配置爲天天放量200個。這種錯誤是測試同窗比較難測試出來的,等到活動真正上線,禮包發放到101個的時候,就報錯了,由於資源池當天已經沒有資源了。雖然,咱們的業務告警系統可以快速捕獲到這個異常(每10分鐘爲一個週期,從十多個維度,監控和計算各個活動的成功率、流量波動等等數據),可是,對於騰訊的用戶量級來講,即便隻影響十多分鐘,也能夠影響成千上萬的用戶,對於大規模流量的推廣活動,甚至能夠影響數十萬用戶了。這樣的話,就很容易就形成嚴重的「現網事故」。
完善的監控系統可以及時發現問題,防止影響面的進一步擴大和失控,可是,它並不能杜絕現網問題的發生。而真正的根治之法,固然是從起源的地方杜絕這種場景的出現,回到上面「日限量配置錯誤」的例子場景中,用戶在內部管理端發佈活動配置時,就直接提示運營同窗,這個配置規則是不對的。
在業界,由於配置參數錯誤而致使的現網重大事故的例子,能夠說是多不勝數,「配置參數問題」幾乎能夠說是一個業界難題,對於解決或者緩解這種錯誤的發生,並無放之四海而皆準的方法,更多的是須要根據具體業務和系統場景,亦步亦趨地逐步建設配套的檢查機制程序或者腳本。
所以,咱們建設了一套強大而且智能的配置檢查系統,裏面集合了數十種業務的搭配檢查規則,而且檢查規則的數目一直都在增長。這裏規則包括檢查禮包日限量之類比較簡單的規則,也有檢查各類關聯配置參數、相對比較複雜的業務邏輯規則。
另一方面,流程的執行不能經過「口頭約定」,也應該固化爲平臺程序的一部分,例如,活動上線以前,咱們要求負責活動的同事須要驗證一下「禮包領取邏輯」,也就是真實的去領取一次禮包。然而,這只是一個「口頭約定」,實際上並不具有強制執行力,若是這位同事由於活動的禮包過多,而漏過其中一個禮包的驗證流程,這種事情也的確偶爾會發生,這個也算是「人的失誤」的另一種場景。
爲了解決問題,這個流程在咱們AMS的內部管理端中,是經過程序去保證的,確保這位同事的QQ號碼的確領取過所有的禮包。作法其實挺簡單的,就是讓負責活動的同事設置一個驗證活動的QQ號碼,而後,程序在發貨活動時,程序會自動檢查每個子活動項目中,是否有這個QQ號碼的活動參與記錄。若是都有參與記錄,則說明這位同事完整地領取了所有禮包。同時,其餘模塊的驗證和測試,咱們也都採用程序和平臺來保證,而不是經過「口頭約定」。
經過程序和系統對業務邏輯和流程的保證,儘量防止「人的失誤」。
這種業務配置檢查程序,除了能夠減小問題的發生,實際上也減輕了測試和驗證活動的工做,能夠起到節省人力的效果。不過,業務配置檢查規則的建設並不簡單,邏輯每每比較複雜,由於要防止誤殺。7、 小結
不管是人仍是機器,都是會產生「失誤」,只是對於單一個體,發生的機率一般並不大。可是,若是一個系統擁有數百臺服務器,或者有一項工做有幾百人共同參與,這種「失誤「的機率就被大大提高,失誤極可能就變爲一種常態了。機器的故障,儘量讓系統自己去兼容和恢復,人的失誤,儘量經過程序和系統流程來避免,都儘量作到」不依賴於人「。
容錯的核心價值,除了加強系統的健壯性外,我以爲是解放技術人員,儘量讓咱們不用凌晨起來處理告警,或享受一個相對平凡閒暇的週末。對於咱們來講,要徹底作到這點,還有很長的路要走,與君共勉。前端