微服務(wù)架構(gòu)讓邊界設(shè)計(jì)良好的服務(wù)的失效互不影響成為可能。
和所有的分布式系統(tǒng)一樣存在網(wǎng)絡(luò)、硬件、應(yīng)用級(jí)別的問(wèn)題。
微服務(wù)架構(gòu)的風(fēng)險(xiǎn)
用網(wǎng)絡(luò)通信代替內(nèi)存中的方法調(diào)用,延遲高、復(fù)雜性高。
微服務(wù)的好處是團(tuán)隊(duì)可以單獨(dú)設(shè)計(jì)、開(kāi)發(fā)、部署服務(wù)。相應(yīng)的,所依賴的服務(wù)也不受團(tuán)隊(duì)成員控制。
需要牢記在心的是:微服務(wù)可能會(huì)臨時(shí)失效(不管是配置出錯(cuò),還是別的什么)
優(yōu)雅地退化服務(wù)
一般情況下,一個(gè)服務(wù)失效并不會(huì)影響其他服務(wù)。比如圖片上傳服務(wù)失效,但是瀏覽圖片的服務(wù)未失效。
但是互相依賴的服務(wù),如果沒(méi)有適當(dāng)?shù)墓收限D(zhuǎn)移(failover)邏輯,就可能全部失效。
管理“變更”
Google的站點(diǎn)可靠性團(tuán)隊(duì)發(fā)現(xiàn)大約70%的服務(wù)中斷是由“變更”引起的。
實(shí)現(xiàn)一個(gè)管理“變更”的策略和自動(dòng)部署系統(tǒng)。
舉例:新代碼在實(shí)例的子集中逐步的實(shí)施,并監(jiān)控它們。如果在關(guān)鍵指標(biāo)上有副作用,自動(dòng)回滾到原來(lái)的版本。
舉例:運(yùn)行兩個(gè)生產(chǎn)環(huán)境,新代碼上線時(shí)先部署在一個(gè)里,只有在確信新版本運(yùn)行正常后才把負(fù)載均衡指向新版本。這叫“藍(lán)綠部署”或“紅黑部署”。
回滾代碼不是壞事。不要把問(wèn)題代碼留在生產(chǎn)環(huán)境然后思考到底哪里出了問(wèn)題。只要有回滾的必要,那就回滾,越快越好。
健康檢查和負(fù)載均衡
負(fù)載均衡要自動(dòng)地跳過(guò)不健康的實(shí)例。檢查健康度可以通過(guò)輪詢/health
端點(diǎn),或者實(shí)例自己上報(bào)狀態(tài)。
現(xiàn)在的服務(wù)發(fā)現(xiàn)方案已經(jīng)可以不斷地收集健康狀況并把負(fù)載均衡的流量分配到健康的組件。
自我治愈
“自我治愈”就是一個(gè)應(yīng)用通過(guò)必要的步驟,從失效狀態(tài)中恢復(fù)。通常是一個(gè)外部的監(jiān)視系統(tǒng),發(fā)現(xiàn)實(shí)例長(zhǎng)時(shí)間處于失效狀態(tài)時(shí),重啟它。
不過(guò)有時(shí)也會(huì)陷入不斷重啟服務(wù)的問(wèn)題。比如數(shù)據(jù)庫(kù)鏈接失敗,這時(shí)會(huì)比較復(fù)雜,需要很多額外的邏輯來(lái)捕獲邊界情況,讓外部監(jiān)視系統(tǒng)知道此時(shí)不必立即重啟實(shí)例。
故障轉(zhuǎn)移緩存
有時(shí)網(wǎng)絡(luò)故障或系統(tǒng)變更會(huì)引起服務(wù)中斷,好在有了負(fù)載均衡和自我治愈,這些中斷都是短暫的。
故障轉(zhuǎn)移緩存通常使用兩個(gè)不同的到期時(shí)間,短的那個(gè)告訴你通常情況下你可以使用多久的緩存;長(zhǎng)的那個(gè)告訴你在失效時(shí)你可以使用多久的緩存。
要注意的是只有在“過(guò)期的數(shù)據(jù)比沒(méi)有數(shù)據(jù)好”這種情況下才能使用故障轉(zhuǎn)移緩存。
使用標(biāo)準(zhǔn)的HTTP頭就能設(shè)置緩存和故障轉(zhuǎn)移緩存。max-age
指定緩存的最大有效時(shí)間。stale-if-error
指定在失效狀態(tài)時(shí)緩存的服務(wù)時(shí)間。
大多數(shù)CDN和負(fù)載均衡都能提供這樣的功能。
重試的邏輯
要小心的使用重試,有時(shí)候大量重試不但不能恢復(fù)服務(wù),還會(huì)使?fàn)顩r更糟。
在分布式系統(tǒng)中,重試會(huì)出發(fā)很多其他的請(qǐng)求,并帶來(lái)級(jí)聯(lián)效應(yīng)。要最小化重試帶來(lái)的影響,要限制重試的次數(shù),并用指數(shù)退避算法來(lái)加大重試的間隔,直到達(dá)到最大重試次數(shù)。
當(dāng)重試是由客戶端(瀏覽器、其他微服務(wù)等)發(fā)起的,并且客戶端在處理請(qǐng)求的前后并不知道操作失敗了,這時(shí)要讓?xiě)?yīng)用冪等地處理重試。所謂冪等就是多次操作和一次操作的結(jié)果是一樣的。比如:用戶重試一個(gè)購(gòu)買操作,應(yīng)用不能對(duì)這個(gè)顧客收費(fèi)兩次。對(duì)每一個(gè)事務(wù)使用冪等令牌能幫你處理這種問(wèn)題。
速率限制器和負(fù)載卸載器
速率限制就是按時(shí)間段對(duì)用戶所能發(fā)起和處理的請(qǐng)求數(shù)目做限制的技術(shù)。舉個(gè)例子:有了速率限制,就能找到哪些用戶和微服務(wù)達(dá)到了流量峰值;或者你可以確定你的應(yīng)用沒(méi)有過(guò)載。還可以對(duì)低優(yōu)先級(jí)的流量進(jìn)行控制,使重要事務(wù)得到足夠的資源。
還有一種并發(fā)請(qǐng)求限制器。
一個(gè)fleet usage load shedder能夠保證重要的事務(wù)總是有足夠的資源可以使用。它保留一些資源給高優(yōu)先級(jí)的請(qǐng)求,并且不允許低優(yōu)先級(jí)的事務(wù)使用全部的這些資源。負(fù)載卸載器不單單靠單一用戶的請(qǐng)求數(shù)量的大小做判斷,而是基于整個(gè)系統(tǒng)的狀態(tài)。負(fù)載卸載器能幫你恢復(fù)系統(tǒng),因?yàn)樵谟袥_突時(shí),它能保持核心功能正常工作。
快速而獨(dú)立地失效
使用艙壁模式(bulkhead pattern)將問(wèn)題隔離在服務(wù)的級(jí)別。
組件要快速失效,而不是等到組件超時(shí)并破壞了應(yīng)用實(shí)例后才失效。沒(méi)有什么比遲遲收不到回應(yīng)和一個(gè)無(wú)響應(yīng)的UI更讓人失望了。
為每一個(gè)服務(wù)設(shè)置一個(gè)超時(shí)時(shí)間,不是一個(gè)好方法。因?yàn)槟慊旧喜荒芙o出“好的”超時(shí)時(shí)間。這是個(gè)反模式,要避免。
使用依賴于對(duì)操作成功/失敗的統(tǒng)計(jì)的斷路器模式(circuit-breaker pattern)來(lái)代替使用超時(shí)時(shí)間。
艦璧
艦璧就是用來(lái)把船體分成一個(gè)一個(gè)部分,當(dāng)其中一個(gè)破裂,就可以把這個(gè)破裂的部分封閉起來(lái)而不影響別的部分。
艦璧的概念可以應(yīng)用到軟件開(kāi)發(fā)中來(lái)隔離資源。這樣就能保護(hù)有限的資源不被耗盡。比如:我們有兩種操作要連接一個(gè)限制了連接數(shù)的數(shù)據(jù)庫(kù),使用兩個(gè)連接池要好過(guò)共用一個(gè)。當(dāng)一個(gè)操作超時(shí)或被濫用時(shí),不會(huì)造成兩個(gè)操作都不可用。
斷路器
可以用超時(shí)機(jī)制限制操作的間隔。當(dāng)我們?cè)谝粋€(gè)高度動(dòng)態(tài)的環(huán)境時(shí),這是個(gè)反模式,因?yàn)槟銕缀醪豢赡転槊恳环N情況都給出恰當(dāng)?shù)某瑫r(shí)時(shí)間。
斷路器模式的名字來(lái)源于電路系統(tǒng)中真實(shí)組件,它們有相同的行為。當(dāng)一個(gè)特定類型的錯(cuò)誤出現(xiàn)多次時(shí),斷路器就開(kāi)啟了。開(kāi)啟斷路器會(huì)防止更多這樣的請(qǐng)求被生成(就像真實(shí)的斷路器阻斷電流那樣)。一般在一定時(shí)間后斷路器會(huì)自動(dòng)關(guān)閉,給底層服務(wù)足夠的時(shí)間去恢復(fù)。
記住:不是所有的錯(cuò)誤都必須觸發(fā)斷路器。例如:你想給客戶端發(fā)送一個(gè)4xx
的響應(yīng)碼,但是附帶一個(gè)5xx
的服務(wù)器端錯(cuò)誤。
有些斷路器還可以有一個(gè)半開(kāi)啟的狀態(tài)。這時(shí)服務(wù)先發(fā)送一個(gè)請(qǐng)求去檢查系統(tǒng)是否是可用的,如果這個(gè)請(qǐng)求成功了,斷路器就關(guān)閉使流量流通,否則斷路器保持開(kāi)啟。
失效測(cè)試
你要不斷地對(duì)系統(tǒng)常見(jiàn)的問(wèn)題進(jìn)行測(cè)試,以使你的服務(wù)能夠在多種失效情況下繼續(xù)存活。要讓團(tuán)隊(duì)成員時(shí)刻準(zhǔn)備好面對(duì)突發(fā)事件。
怎么測(cè)試呢?可以使用一個(gè)外部的服務(wù)按組標(biāo)記服務(wù)實(shí)例,隨機(jī)地中斷分組中的一個(gè)實(shí)例。你可以準(zhǔn)備一個(gè)單點(diǎn)失效,也可以停止一個(gè)區(qū)域的實(shí)例來(lái)模擬云供應(yīng)商運(yùn)行中斷。
Netflix的<a target="_blank">ChaosMonkey</a>是個(gè)不錯(cuò)的測(cè)試方案。
結(jié)論
實(shí)現(xiàn)并運(yùn)行一個(gè)可靠的服務(wù)并不容易。這需要花費(fèi)很多精力和金錢。
“可靠”也有很多級(jí)別和方面。找到適用于團(tuán)隊(duì)的最佳方案,分配足夠的預(yù)算和時(shí)間。
關(guān)鍵點(diǎn)
- 動(dòng)態(tài)的環(huán)境和分布式系統(tǒng)會(huì)有很高的幾率失效,比如微服務(wù)。
- 服務(wù)的失效應(yīng)該是獨(dú)立的,能夠優(yōu)雅的“退化”,以提高用戶體驗(yàn)。
- 70%的中斷是由變更引起的,回滾代碼不是壞事。
- 讓失效快速且獨(dú)立。團(tuán)隊(duì)成員對(duì)依賴的服務(wù)沒(méi)有控制權(quán)。
- 類似緩存、艦璧、斷路器、速率限制器等架構(gòu)模式和技術(shù)能幫你構(gòu)建可靠的微服務(wù)。
原文鏈接
- <a target="_blank">Designing a Microservices Architecture for Failure</a>