億級(jí)Web系統(tǒng)的容錯(cuò)性建設(shè)實(shí)踐
三年多前,我在騰訊負(fù)責(zé)的活動(dòng)運(yùn)營系統(tǒng),因?yàn)闃I(yè)務(wù)流量規(guī)模的數(shù)倍增長,系統(tǒng)出現(xiàn)了各種各樣的異常,當(dāng)時(shí),作為開發(fā)的我,7*24小時(shí)地沒日沒夜處理 告警,周末和凌晨也經(jīng)常上線,疲于奔命。后來,當(dāng)時(shí)的老領(lǐng)導(dǎo)對我說:你不能總扮演一個(gè)“救火隊(duì)長”的角色, 要嘗試從系統(tǒng)整體層面思考產(chǎn)生問題的根本原 因,然后推進(jìn)解決。
我幡然醒悟,“火”是永遠(yuǎn)救不完的,讓系統(tǒng)能夠自動(dòng)”滅火”,才是解決問題的正確方向。簡而言之,系統(tǒng)的異常不能總是依賴于“人”去恢復(fù),讓系統(tǒng)本 身具備“容錯(cuò)”能力,才是根本解決之道。三年多過去了,我仍然負(fù)責(zé)著這個(gè)系統(tǒng),而它也已經(jīng)從一個(gè)日請求百萬級(jí)的小Web系統(tǒng),逐步成長為一個(gè)高峰日請求達(dá) 到8億規(guī)模的平臺(tái)級(jí)系統(tǒng),走過一段令人難忘的技術(shù)歷程。
容錯(cuò)其實(shí)是系統(tǒng)健壯性的重要指標(biāo)之一,而本文會(huì)主要聚焦于“容錯(cuò)”能力的實(shí)踐,希望對做技術(shù)的同學(xué)有所啟發(fā)和幫助。
(備注:QQ會(huì)員活動(dòng)運(yùn)營平臺(tái),后面統(tǒng)一簡稱AMS)
一、 重試機(jī)制
最容易也最簡單被人想到的容錯(cuò)方式,當(dāng)然就是“失敗重試”,總而言之,簡單粗暴!簡單是指它的實(shí)現(xiàn)通常很簡單,粗暴則是指使用不當(dāng),很可能會(huì)帶來系統(tǒng)“雪崩”的風(fēng)險(xiǎn),因?yàn)橹卦囈馕吨鴮蠖朔?wù)的雙倍請求。
1. 簡單重試
我們請求一個(gè)服務(wù),如果服務(wù)請求失敗,則重試一次。假設(shè),這個(gè)服務(wù)在常規(guī)狀態(tài)下是99.9%的成功率,因?yàn)槟骋淮尾▌?dòng)性的異常,成功率下跌到 95%,那么如果有重試機(jī)制,那么成功率大概還能保持在99.75%。而簡單重試的缺陷也很明顯,如果服務(wù)真的出問題,很可能帶來雙倍流量,沖擊服務(wù)系 統(tǒng),有可能直接將服務(wù)沖垮。而在實(shí)際的真實(shí)業(yè)務(wù)場景,往往更嚴(yán)重,一個(gè)功能不可用,往往更容易引起用戶的“反復(fù)點(diǎn)擊”,反而制造更大規(guī)模的流量沖擊。比起 服務(wù)的成功率比較低,系統(tǒng)直接被沖擊到“掛掉”的后果明顯更嚴(yán)重。
簡單重試,要使用在恰當(dāng)?shù)膱鼍?。或者,主?dòng)計(jì)算服務(wù)成功率,成功率過低,就直接不做重試行為,避免帶來過高的流量沖擊。
2. 主備服務(wù)自動(dòng)切換
既然單一服務(wù)的重試,可能會(huì)給該帶來雙倍的流量沖擊,而最終導(dǎo)致更嚴(yán)重的后果,那么我們不如將場景變?yōu)橹鱾浞?wù)的自動(dòng)重試或者切換。例如,我們搭建 了兩套獲取openid的服務(wù),如果服務(wù)A獲取失敗,則嘗試從服務(wù)B中獲取。因?yàn)橹卦嚨恼埱髩毫κ菈旱搅朔?wù)B上,服務(wù)A通常不會(huì)因?yàn)橹卦嚩a(chǎn)生雙倍的流 量沖擊。
這種重試的機(jī)制,看似比較可用,而實(shí)際上也存在一些問題:
(1) 通常會(huì)存在“資源浪費(fèi)”的問題。因?yàn)閭浞莘?wù)系統(tǒng),很可能長期處于閑置狀態(tài),只有在主服務(wù)異常的時(shí)候,它的資源才會(huì)被比較充分地使用。不 過,如果對于核心的服務(wù)業(yè)務(wù)(例如核心數(shù)據(jù)、營收相關(guān))進(jìn)行類似的部署,雖然會(huì)增加一些機(jī)器成本和預(yù)算,但這個(gè)付出通常也是物有所值的。
(2) 觸發(fā)重試機(jī)制,對于用戶的請求來說,耗時(shí)必然增加。主服務(wù)請求失敗,然后再到備份服務(wù)請求,這個(gè)環(huán)節(jié)的請求耗時(shí)就至少翻倍增長,假設(shè)主服務(wù) 出現(xiàn)連接(connect)超時(shí),那么耗時(shí)就更是大幅度增加。一個(gè)服務(wù)在正常狀態(tài)下,獲取數(shù)據(jù)也許只要50ms,而服務(wù)的超時(shí)時(shí)間通常會(huì)設(shè)置到 500-1000ms,甚至更多,一旦出現(xiàn)超時(shí)重試的場景,請求耗時(shí)必然大幅度增長,很可能會(huì)比較嚴(yán)重地影響用戶體驗(yàn)。
(3) 主備服務(wù)一起陷入異常。如果是因?yàn)榱髁窟^大問題導(dǎo)致主服務(wù)異常,那么備份服務(wù)很可能也會(huì)承受不住這種級(jí)別的流量而掛掉。
重試的容錯(cuò)機(jī)制,在AMS上有使用,但是相對比較少,因?yàn)槲覀冋J(rèn)為主備服務(wù),還是不足夠可靠。
二、 動(dòng)態(tài)剔除或者恢復(fù)異常機(jī)器
在AMS里,我們的后端涉及數(shù)以百計(jì)的各類服務(wù),來支撐整個(gè)運(yùn)營系統(tǒng)的正常運(yùn)作。所有后端服務(wù)或者存儲(chǔ),首先是部署為無狀態(tài)的方式提供服務(wù)(一個(gè)服務(wù)通常很多臺(tái)機(jī)器),然后,通過公司內(nèi)的一個(gè)公共的智能路由服務(wù)L5,納入到AMS中。
(1) 所有服務(wù)與存儲(chǔ),無狀態(tài)路由。這樣做的目的,主要是為了避免單點(diǎn)風(fēng)險(xiǎn),就是避免某個(gè)服務(wù)節(jié)點(diǎn)掛了,導(dǎo)致整個(gè)服務(wù)就癱瘓了。實(shí)際上,即使像一 些具有主備性質(zhì)(主機(jī)器掛了,支持切換到備份機(jī)器)的接入服務(wù),也是不夠可靠的,畢竟只有2臺(tái),它們都掛了的情況,還是可能發(fā)生的。我們后端的服務(wù),通常 都以一組機(jī)器的形式提供服務(wù),彼此之間沒有狀態(tài)關(guān)系,支撐隨機(jī)分配請求。
(2) 支持平行擴(kuò)容。遇到大流量場景,支持加機(jī)器擴(kuò)容。
(3) 自動(dòng)剔除異常機(jī)器。在我們的路由服務(wù),發(fā)現(xiàn)某個(gè)服務(wù)的機(jī)器異常的時(shí)候(成功率低于50%),就會(huì)自動(dòng)剔除該機(jī)器,后續(xù),會(huì)發(fā)出試探性的請求,確認(rèn)等它恢復(fù)正常之后,再重新加回到服務(wù)機(jī)器組。
例如,假如一組服務(wù)下?lián)碛蟹?wù)機(jī)器四臺(tái)(ABCD),假設(shè)A機(jī)器的服務(wù)因?yàn)槟撤N未知原因,完全不可用了,這個(gè)時(shí)候L5服務(wù)會(huì)主動(dòng)將A機(jī)器自動(dòng)從服務(wù) 組里剔除,只保留BCD三臺(tái)機(jī)器對外提供服務(wù)。而在后續(xù),假如A機(jī)器從異常中恢復(fù)了,那么L5再主動(dòng)將機(jī)器A加回來,最后,又變成ABCD四臺(tái)機(jī)器對外提 供服務(wù)。
在過去的3年里,我們逐步將AMS內(nèi)的服務(wù),漸漸從寫死IP列表或者主備狀態(tài)的服務(wù),全部升級(jí)和優(yōu)化為L5模式的服務(wù),慢慢實(shí)現(xiàn)了AMS后端服務(wù)的 自我容錯(cuò)能力。至少,我們已經(jīng)比較少遇到,再因?yàn)槟骋慌_(tái)機(jī)器的軟件或者硬件故障,而不得不人工介入處理的情況。我們也慢慢地從疲于奔命地處理告警的苦難 中,被解放出來。
三、 超時(shí)時(shí)間
1. 為服務(wù)和存儲(chǔ)設(shè)置合理的超時(shí)時(shí)間
調(diào)用任何一個(gè)服務(wù)或者存儲(chǔ),一個(gè)合理的超時(shí)時(shí)間(超時(shí)時(shí)間,就是我們請求一個(gè)服務(wù)時(shí),等待的最長時(shí)間),是非常重要的,而這一點(diǎn)往往比較容易被忽視。通常Web系統(tǒng)和后端服務(wù)的通信方式,是同步等待的模式。這種模式,它會(huì)帶來的問題比較多。
對于服務(wù)端,影響比較大的一個(gè)問題,就是它會(huì)嚴(yán)重影響系統(tǒng)吞吐率。假設(shè),我們一個(gè)服務(wù)的機(jī)器上,啟用了100個(gè)處理請求的 worker,worker的超時(shí)時(shí)間設(shè)置為5秒,1個(gè)worker處理1個(gè)任務(wù)的平均處理耗時(shí)是100ms。那么1個(gè)work在5秒鐘的時(shí)間里,能夠處 理50個(gè)用戶請求,然而,一旦網(wǎng)絡(luò)或者服務(wù)偶爾異常,響應(yīng)超時(shí),那么在本次處理的后續(xù)整整5秒里,它僅僅處理了1個(gè)等待超時(shí)的失敗任務(wù)。一旦比較大概率出 現(xiàn)這類型的超時(shí)異常,系統(tǒng)的吞吐率就會(huì)大面積下降,有可能耗盡所有的worker(資源被占據(jù),全部在等待狀態(tài),直到5s超時(shí)才釋放),最終導(dǎo)致新的請求 無worker可用,只能陷入異常狀態(tài)。
算上網(wǎng)絡(luò)通信和其他環(huán)節(jié)的耗時(shí),用戶就等待了超過5s時(shí)間,最后卻獲得一個(gè)異常的結(jié)果,用戶的心情通常是崩潰的。
解決這個(gè)問題的方式,就是設(shè)置一個(gè)合理的超時(shí)時(shí)間。例如,回到上面的的例子,平均處理耗時(shí)是100ms,那么我們不如將超時(shí)時(shí)間從5s下調(diào)到 500ms。從直觀上看,它就解決了吞吐率下降和用戶等待過長的問題。然而,這樣做本身又比較容易帶來新的問題,就是會(huì)引起服務(wù)的成功率下降。因?yàn)槠骄?時(shí)是100ms,但是,部分業(yè)務(wù)請求本身耗時(shí)比較長,耗時(shí)超過500ms也比較多。例如,某個(gè)請求服務(wù)端耗時(shí)600ms才處理完畢,然后這個(gè)時(shí)候,客戶端 認(rèn)為等待超過500ms,已經(jīng)斷開了連接。處理耗時(shí)比較長的這類型業(yè)務(wù)請求會(huì)受到比較明顯的影響。
2. 超時(shí)時(shí)間設(shè)置過短帶來的成功率下降
超時(shí)時(shí)間設(shè)置過短,會(huì)將很多本來處理成功的請求,當(dāng)做服務(wù)超時(shí)處理掉,進(jìn)而引起服務(wù)成功率下降。將全部業(yè)務(wù)服務(wù),以一刀切的方式設(shè)置一個(gè)超時(shí)時(shí)間,是比較不可取的。優(yōu)化的方法,我們分為兩個(gè)方向。
(1) 快慢分離
根據(jù)實(shí)際的業(yè)務(wù)維度,區(qū)分對待地給各個(gè)業(yè)務(wù)服務(wù)配置不同的超時(shí)時(shí)間,同時(shí),最好也將它們的部署服務(wù)也分離出來。例如,天天酷跑的查詢服務(wù)耗時(shí)通常為 100ms,那么超時(shí)時(shí)間我們就設(shè)置為1s,某新手游的查詢服務(wù)通常耗時(shí)為700ms,那么我們就設(shè)置為5s。這樣的話,整體系統(tǒng)的成功率,就不會(huì)受到比 較大的影響。
(2) 解決同步阻塞等待
“快慢分離”可以改善系統(tǒng)的同步等待問題,但是,對于某些耗時(shí)本來就比較長的服務(wù)而言,系統(tǒng)的進(jìn)程/線程資源仍然在同步等待過程中,無法響應(yīng)其他新的請求,只能阻塞等待,它的資源仍然是被占據(jù),系統(tǒng)的整體吞吐率仍然被大幅度拉低。
解決的思路,當(dāng)然是利用I/O多路復(fù)用,通過異步回調(diào)的方式,解決同步等待過程中的資源浪費(fèi)。AMS的一些核心服務(wù),采用的就是“協(xié)程”(又叫“微 線程”,簡單的說,常規(guī)異步程序代碼里嵌套比較多層的函數(shù)回調(diào),編寫復(fù)雜。而協(xié)程則提供了一種類似寫同步代碼的方式,來寫異步回調(diào)程序),以解決同步等待 的問題。異步處理的簡單描述,就是當(dāng)進(jìn)程遇到I/O網(wǎng)絡(luò)阻塞時(shí),就保留現(xiàn)場,立刻切換去處理下一個(gè)業(yè)務(wù)請求,進(jìn)程不會(huì)因?yàn)槟硞€(gè)網(wǎng)絡(luò)等待而停止處理業(yè)務(wù),進(jìn) 而,系統(tǒng)吞吐率即使遇到網(wǎng)絡(luò)等待時(shí)間過長的場景,通常都能保持在比較高的水平。
值得補(bǔ)充一點(diǎn)的是,異步處理只是解決系統(tǒng)的吞吐率問題,對于用戶的體驗(yàn)問題,并不會(huì)有改善,用戶需要等待的時(shí)間并不會(huì)減少。
3. 防重入,防止重復(fù)發(fā)貨
前面我們提到,我們設(shè)置了一個(gè)比較“合理的超時(shí)時(shí)間”,簡而言之,就是一個(gè)比較短的超時(shí)時(shí)間。而在數(shù)據(jù)寫入的場景,會(huì)引起新的問題,就我們的AMS系統(tǒng)而言,就是發(fā)貨場景。如果是發(fā)貨請求超時(shí),這個(gè)時(shí)候,我們需要思考的問題就比較多了。
(1) 發(fā)貨等待超時(shí),發(fā)貨服務(wù)執(zhí)行發(fā)貨失敗。這種場景,問題不大,后續(xù)用戶重新點(diǎn)擊領(lǐng)取按鈕,就可以觸發(fā)下一次重新發(fā)貨。
(2) 發(fā)貨等待超時(shí),發(fā)貨服務(wù)實(shí)際在更晚的時(shí)候執(zhí)行發(fā)貨成功,我們稱之為“超時(shí)成功”。比較麻煩的場景,則是每次都是發(fā)貨超時(shí),而實(shí)際上都發(fā)貨成功,如果系統(tǒng)設(shè)計(jì)不當(dāng),有可能導(dǎo)致用戶可以無限領(lǐng)取禮包,最終造成活動(dòng)運(yùn)營事故。
第二種場景,給我們帶來了比較麻煩的問題,如果處理不當(dāng),用戶再次點(diǎn)擊,就觸發(fā)第多次“額外”發(fā)貨。
例如,我們假設(shè)某個(gè)發(fā)貨服務(wù)超時(shí)時(shí)間設(shè)置為6s,用戶點(diǎn)擊按鈕,我們的AMS收到請求后,請求發(fā)貨服務(wù)發(fā)貨,等待6s后,無響應(yīng),我們給用戶提示 “領(lǐng)取失敗”,而實(shí)際上發(fā)貨服務(wù)卻在第8秒執(zhí)行發(fā)貨成功,禮包到了用戶的賬戶上。而用戶看見“領(lǐng)取失敗”,則又再次點(diǎn)擊按鈕,最終導(dǎo)致“額外”多發(fā)一個(gè)禮 包給到這個(gè)用戶。
例子的時(shí)序和流程圖大致如下:
這里就提到了防重入,簡單的說,就是如何確認(rèn)不管用戶點(diǎn)擊多少次這個(gè)領(lǐng)取按鈕,我們都確保結(jié)果只有一種預(yù)期結(jié)果,就是只會(huì)給用戶發(fā)一次禮包,而不引 起重復(fù)發(fā)貨。我們的AMS活動(dòng)運(yùn)營平臺(tái)一年上線的活動(dòng)超過4000個(gè),涉及數(shù)以萬計(jì)的各種類型、不同業(yè)務(wù)系統(tǒng)的禮包發(fā)貨,業(yè)務(wù)通信場景比較復(fù)雜。針對不同 的業(yè)務(wù)場景,我們做了不同的解決方案:
(1) 業(yè)務(wù)層面限制,設(shè)置禮包單用戶限量。在發(fā)貨服務(wù)器的源頭,設(shè)置好一個(gè)用戶僅能最多獲得1個(gè)禮包,直接避免重復(fù)發(fā)放。但是,這種業(yè)務(wù)限制,并非每個(gè)業(yè)務(wù)場景都通用的,只限于內(nèi)部具備該限制能力的業(yè)務(wù)發(fā)貨系統(tǒng),并且,有一些禮包本身就可以多次領(lǐng)取的,就不適用了。
(2) 訂單號(hào)機(jī)制。用戶的每一次符合資格的發(fā)貨請求,都生成一個(gè)訂單號(hào)與之對應(yīng),通過它來確保1個(gè)訂單號(hào),只發(fā)貨1次。這個(gè)方案雖然比較完善,但 是,它是依賴于發(fā)貨服務(wù)方配合做“訂單號(hào)發(fā)貨狀態(tài)更新“的,而我們的發(fā)貨業(yè)務(wù)方眾多,并非每一個(gè)都能支持”訂單號(hào)更新“的場景。
(3) 自動(dòng)重試的異步發(fā)貨模式。用戶點(diǎn)擊領(lǐng)取禮包按鈕后,Web端直接返回成功,并且提示禮包在30分鐘內(nèi)到賬。對于后臺(tái),則將該發(fā)貨錄入到發(fā)貨 隊(duì)列或者存儲(chǔ)中,等待發(fā)貨服務(wù)異步發(fā)貨。因?yàn)槭钱惒教幚?,可以多次?zhí)行發(fā)貨重試操作,直到發(fā)貨成功為止。同時(shí),異步發(fā)貨是可以設(shè)置一個(gè)比較長的超時(shí)等待時(shí) 間,通常不會(huì)出現(xiàn)“超時(shí)成功”的場景,并且對于前端響應(yīng)來說,不需要等待后臺(tái)發(fā)貨狀態(tài)的返回。但是,這種模式,會(huì)給用戶帶來比較不好的體驗(yàn),就是沒有實(shí)時(shí) 反饋,無法立刻告訴用戶,禮包是否到賬。
4. 非訂單號(hào)的特殊防刷機(jī)制
某些特殊的合作場景,我們無法使用雙方約定訂單號(hào)方式,例如一個(gè)完全隔離獨(dú)立的外部發(fā)貨接口,不能和我們做訂單號(hào)的約定?;谶@種場景,我們AMS 專門做了一種防刷的機(jī)制,就是通過限制read超時(shí)的次數(shù)。但是,這種方案并非完美解決重復(fù)發(fā)貨問題,只是能起到夠盡可能減少避免被刷的作用。一次網(wǎng)絡(luò)通 信,通常包含:建立連接(connect),寫入數(shù)據(jù)發(fā)包(write),等待并且讀取回包(read),斷開連接(close)。
通常一個(gè)發(fā)貨服務(wù)如果出現(xiàn)異常,大多數(shù)情況,在connect步驟就是失敗或者超時(shí),而如果一個(gè)請求走到等待回包(read)時(shí)超時(shí),那么發(fā)貨服務(wù) 另外一邊就有可能發(fā)生了“超時(shí)但發(fā)貨成功”的場景。這個(gè)時(shí)候,我們將read超時(shí)的發(fā)生次數(shù)記錄起來,然后提供了一個(gè)配置限制次數(shù)的能力。假如設(shè)置為2 次,那么當(dāng)一個(gè)用戶第一次領(lǐng)取禮包,遇到read超時(shí),我們就允許它重試,當(dāng)還遇到第二次read超時(shí),就達(dá)到我們之前設(shè)置的閥值2,我們就認(rèn)為它可能發(fā) 貨成功,拒絕用戶的第三次領(lǐng)取請求。
這種做法,假設(shè)發(fā)貨服務(wù)真的出現(xiàn)很多超時(shí)成功,那么用戶也最多只能刷到2次禮包(次數(shù)可配置),而避免發(fā)生禮包無限制被刷的場景。但是,這種方案并不完全可靠,謹(jǐn)慎使用。
在發(fā)貨場景,還會(huì)涉及分布式場景下的CAP(一致性、可用性、分區(qū)容錯(cuò)性)問題,不過,我們的系統(tǒng)并非是一個(gè)電商服務(wù),大部分的發(fā)貨并沒有強(qiáng)烈的一 致性要求。因此,總體而言,我們是弱化了一致性問題(核心服務(wù),通過異步重試的方式,達(dá)到最終一致性),以追求可用性和分區(qū)容錯(cuò)性的保證。
四、 服務(wù)降級(jí),自動(dòng)屏蔽非核心分支異常
對于一次禮包領(lǐng)取請求,在我們的后端CGI會(huì)經(jīng)過10多個(gè)環(huán)節(jié)和服務(wù)的邏輯判斷,包括禮包配置讀取、禮包限量檢查、登陸態(tài)校驗(yàn)、安全保護(hù)等等。而這 些服務(wù)中,就有不可以跳過的核心環(huán)節(jié),例如讀取禮包配置的服務(wù),也有非核心環(huán)節(jié),例如數(shù)據(jù)上報(bào)。對于非核心環(huán)節(jié),我們的做法,就是設(shè)置一個(gè)比較低的超時(shí)時(shí) 間。
例如我們其中一個(gè)統(tǒng)計(jì)上報(bào)服務(wù),平均耗時(shí)是3ms,那么我們就將超時(shí)時(shí)間設(shè)置為20ms,一旦超時(shí)則旁路掉,繼續(xù)按照正常邏輯走業(yè)務(wù)流程。
五、 服務(wù)解耦、物理隔離
雖然,大家都知道一個(gè)服務(wù)的設(shè)計(jì),要盡可能小和分離部署,如此,服務(wù)之間的耦合會(huì)比較小,一旦某個(gè)模塊出問題,受到影響的模塊就比較少,容錯(cuò)能力就 會(huì)更強(qiáng)??墒牵瑥脑O(shè)計(jì)之初,就將每一個(gè)服務(wù)有序的切割地很小,這個(gè)需要設(shè)計(jì)者具備超前的意識(shí),能夠提前意識(shí)到業(yè)務(wù)和系統(tǒng)的發(fā)展形態(tài),而實(shí)際上,業(yè)務(wù)的發(fā)展 往往是比較難以預(yù)知的,因?yàn)闃I(yè)務(wù)的形態(tài)會(huì)隨著產(chǎn)品的策略的改變而變化。在業(yè)務(wù)早期流量比較小的時(shí)候,通常也沒有足夠的人力和資源,將服務(wù)細(xì)細(xì)的切分。 AMS從日請求百萬級(jí)的Web系統(tǒng),逐漸成長為億級(jí),在這個(gè)過程中,流量規(guī)模增長了100倍,我們經(jīng)歷了不少服務(wù)耦合帶來的陣痛。
1. 服務(wù)分離,大服務(wù)變成多個(gè)小服務(wù)
我們常常說,雞蛋不能都放在一個(gè)籃子里。AMS以前是一個(gè)比較小的系統(tǒng)(日請求百萬級(jí),在騰訊公司內(nèi)完全是一個(gè)不起眼的小Web系統(tǒng)),因此,很多 服務(wù)和存儲(chǔ)在早起都是部署在一起的,查詢和發(fā)貨服務(wù)都放在一起,不管哪一個(gè)出問題,都相互影響。后來,我們逐漸的將這些核心的服務(wù)和存儲(chǔ),慢慢地分離出 來,細(xì)細(xì)切分和重新部署。在數(shù)據(jù)存儲(chǔ)方面,我們將原來3-5個(gè)存儲(chǔ)的服務(wù),慢慢地切為20多個(gè)獨(dú)立部署的存儲(chǔ)。
例如,2015年下半年,我們就將其中一個(gè)核心的存儲(chǔ)數(shù)據(jù),從1個(gè)分離為3個(gè)。
這樣做帶來了很多好處:
(1) 原來主存儲(chǔ)的壓力被分流。
(2) 穩(wěn)定性更高,不再是其中一個(gè)出問題,影響整個(gè)大的模塊。
(3) 存儲(chǔ)之間是彼此物理隔離的,即使服務(wù)器硬件故障,也不會(huì)相互影響。
2. 輕重分離,物理隔離
另外一方面,我們對于一些核心的業(yè)務(wù),進(jìn)行“輕重分離”。例如,我們支持2016年“手Q春節(jié)紅包”活動(dòng)項(xiàng)目的服務(wù)集群。就將負(fù)責(zé)信息查詢和紅包禮 包發(fā)貨的集群分別獨(dú)立部署,信息查詢的服務(wù)相對沒有那么重要,業(yè)務(wù)流程比較輕量級(jí),而紅包禮包發(fā)貨則屬于非常核心的業(yè)務(wù),業(yè)務(wù)流程比較重。
輕重分離的這個(gè)部署方式,可以給我們帶來一些好處:
(1) 查詢集群即使出問題,也不會(huì)影響發(fā)貨集群,保證用戶核心功能正常。
(2) 兩邊的機(jī)器和部署的服務(wù)基本一致,在緊急的情況下,兩邊的集群可以相互支援和切換,起到容災(zāi)的效果。
(3) 每個(gè)集群里的機(jī)器,都是跨機(jī)房部署,例如,服務(wù)器都是分布在ABC三個(gè)機(jī)房,假設(shè)B機(jī)房整個(gè)網(wǎng)絡(luò)故障了,反向代理服務(wù)會(huì)將無法接受服務(wù)的B機(jī)房機(jī)器剔除,然后,剩下AC機(jī)房的服務(wù)器仍然可以正常為外界提供服務(wù)。
六、 業(yè)務(wù)層面的容錯(cuò)
如果系統(tǒng)架構(gòu)設(shè)計(jì)層面的“容錯(cuò)”我們都搭建完善了,那么再繼續(xù)下一層容錯(cuò),就需要根據(jù)實(shí)際的業(yè)務(wù)來進(jìn)行,因?yàn)?,不同的業(yè)務(wù)擁有不同的業(yè)務(wù)邏輯特性, 也能夠?qū)е聵I(yè)務(wù)層面的各種問題。而在業(yè)務(wù)層面的容錯(cuò),簡而言之,避免“人的失誤”。不管一個(gè)人做事性格多么謹(jǐn)慎細(xì)心,也總有“手抖”的時(shí)候,在不經(jīng)意間產(chǎn) 生“失誤”。AMS是一個(gè)活動(dòng)運(yùn)營平臺(tái),一個(gè)月會(huì)上線400多個(gè)活動(dòng),涉及數(shù)以千計(jì)的活動(dòng)配置信息(包括禮包、規(guī)則、活動(dòng)參與邏輯等等)。在我們的業(yè)務(wù)場 景下,因?yàn)榉N種原因而導(dǎo)致“人的失誤”并不少。
例如,某個(gè)運(yùn)營同學(xué)看錯(cuò)禮包發(fā)放的日限量,將原本只允許1天放量100個(gè)禮包的資源,錯(cuò)誤地配置為每天放量200個(gè)。這種錯(cuò)誤是測試同學(xué)比較難測試 出來的,等到活動(dòng)真正上線,禮包發(fā)放到101個(gè)的時(shí)候,就報(bào)錯(cuò)了,因?yàn)橘Y源池當(dāng)天已經(jīng)沒有資源了。雖然,我們的業(yè)務(wù)告警系統(tǒng)能夠快速捕獲到這個(gè)異常(每 10分鐘為一個(gè)周期,從十多個(gè)維度,監(jiān)控和計(jì)算各個(gè)活動(dòng)的成功率、流量波動(dòng)等等數(shù)據(jù)),但是,對于騰訊的用戶量級(jí)來說,即使只影響十多分鐘,也可以影響成 千上萬的用戶,對于大規(guī)模流量的推廣活動(dòng),甚至可以影響數(shù)十萬用戶了。這樣的話,就很容易就造成嚴(yán)重的“現(xiàn)網(wǎng)事故”。
完善的監(jiān)控系統(tǒng)能夠及時(shí)發(fā)現(xiàn)問題,防止影響面的進(jìn)一步擴(kuò)大和失控,但是,它并不能杜絕現(xiàn)網(wǎng)問題的發(fā)生。而真正的根治之法,當(dāng)然是從起源的地方杜絕這 種場景的出現(xiàn),回到上面“日限量配置錯(cuò)誤”的例子場景中,用戶在內(nèi)部管理端發(fā)布活動(dòng)配置時(shí),就直接提示運(yùn)營同學(xué),這個(gè)配置規(guī)則是不對的。
在業(yè)界,因?yàn)榕渲脜?shù)錯(cuò)誤而導(dǎo)致的現(xiàn)網(wǎng)重大事故的例子,可以說是多不勝數(shù),“配置參數(shù)問題”幾乎可以說是一個(gè)業(yè)界難題,對于解決或者緩解這種錯(cuò)誤的發(fā)生,并沒有放之四海而皆準(zhǔn)的方法,更多的是需要根據(jù)具體業(yè)務(wù)和系統(tǒng)場景,亦步亦趨地逐步建設(shè)配套的檢查機(jī)制程序或者腳本。
因此,我們建設(shè)了一套強(qiáng)大并且智能的配置檢查系統(tǒng),里面集合了數(shù)十種業(yè)務(wù)的搭配檢查規(guī)則,并且檢查規(guī)則的數(shù)目一直都在增加。這里規(guī)則包括檢查禮包日限量之類比較簡單的規(guī)則,也有檢查各種關(guān)聯(lián)配置參數(shù)、相對比較復(fù)雜的業(yè)務(wù)邏輯規(guī)則。
另外一方面,流程的執(zhí)行不能通過“口頭約定”,也應(yīng)該固化為平臺(tái)程序的一部分,例如,活動(dòng)上線之前,我們要求負(fù)責(zé)活動(dòng)的同事需要驗(yàn)證一下“禮包領(lǐng)取 邏輯”,也就是真實(shí)的去領(lǐng)取一次禮包。然而,這只是一個(gè)“口頭約定”,實(shí)際上并不具備強(qiáng)制執(zhí)行力,如果這位同事因?yàn)榛顒?dòng)的禮包過多,而漏過其中一個(gè)禮包的 驗(yàn)證流程,這種事情也的確偶爾會(huì)發(fā)生,這個(gè)也算是“人的失誤”的另外一種場景。
為了解決問題,這個(gè)流程在我們AMS的內(nèi)部管理端中,是通過程序去保證的,確保這位同事的QQ號(hào)碼的確領(lǐng)取過全部的禮包。做法其實(shí)挺簡單的,就是讓 負(fù)責(zé)活動(dòng)的同事設(shè)置一個(gè)驗(yàn)證活動(dòng)的QQ號(hào)碼,然后,程序在發(fā)貨活動(dòng)時(shí),程序會(huì)自動(dòng)檢查每一個(gè)子活動(dòng)項(xiàng)目中,是否有這個(gè)QQ號(hào)碼的活動(dòng)參與記錄。如果都有參 與記錄,則說明這位同事完整地領(lǐng)取了全部禮包。同時(shí),其他模塊的驗(yàn)證和測試,我們也都采用程序和平臺(tái)來保證,而不是通過“口頭約定”。
通過程序和系統(tǒng)對業(yè)務(wù)邏輯和流程的保證,盡可能防止“人的失誤”。
這種業(yè)務(wù)配置檢查程序,除了可以減少問題的發(fā)生,實(shí)際上也減輕了測試和驗(yàn)證活動(dòng)的工作,可以起到節(jié)省人力的效果。不過,業(yè)務(wù)配置檢查規(guī)則的建設(shè)并不簡單,邏輯往往比較復(fù)雜,因?yàn)橐乐拐`殺。
七、 小結(jié)
無論是人還是機(jī)器,都是會(huì)產(chǎn)生“失誤”,只是對于單一個(gè)體,發(fā)生的概率通常并不大。但是,如果一個(gè)系統(tǒng)擁有數(shù)百臺(tái)服務(wù)器,或者有一項(xiàng)工作有幾百人共 同參與,這種“失誤“的概率就被大大提升,失誤很可能就變?yōu)橐环N常態(tài)了。機(jī)器的故障,盡可能讓系統(tǒng)本身去兼容和恢復(fù),人的失誤,盡可能通過程序和系統(tǒng)流程 來避免,都盡可能做到”不依賴于人“。
容錯(cuò)的核心價(jià)值,除了增強(qiáng)系統(tǒng)的健壯性外,我覺得是解放技術(shù)人員,盡可能讓我們不用凌晨起來處理告警,或享受一個(gè)相對平凡閑暇的周末。對于我們來說,要完全做到這點(diǎn),還有很長的路要走,與君共勉。