2018年,有半年的時(shí)間在做服務(wù)災(zāi)備,由于當(dāng)時(shí)對(duì)這一塊的知識(shí)掌握得比較零碎,直接上手實(shí)踐,沒(méi)有較系統(tǒng)地學(xué)習(xí),在后續(xù)的工作中,通過(guò)不斷實(shí)踐+學(xué)習(xí)補(bǔ)充這一塊的知識(shí),以及反思當(dāng)時(shí)的實(shí)踐,逐漸明白了要做災(zāi)備的原因和這么做的理由。在此寫(xiě)下自己的小小總結(jié)。

為什么要做災(zāi)備?
當(dāng)時(shí)開(kāi)始要做災(zāi)備的原因,是因?yàn)橛幸淮螜C(jī)房A故障了,當(dāng)時(shí)大部分的服務(wù)都不可以用:時(shí)長(zhǎng)上漲、接口失敗,原因是:
1、很多服務(wù)都部署到A機(jī)房了,導(dǎo)致大部分服務(wù)不可用
2、服務(wù)依賴(lài)的數(shù)據(jù)服務(wù)(MySQL、redis)是單點(diǎn)

出現(xiàn)的問(wèn)題表現(xiàn)是:時(shí)長(zhǎng)上漲和接口失敗,導(dǎo)致了頁(yè)面不可用、服務(wù)受損。
這個(gè)問(wèn)題的根本原因是出現(xiàn)服務(wù)單點(diǎn)的情況,沒(méi)有備用的服務(wù)可以切換,導(dǎo)致請(qǐng)求/服務(wù)上游一直等待,等待一定時(shí)間后,就失敗了。
知道問(wèn)題的根本原因后,解決問(wèn)題的核心方向就是解決單點(diǎn)問(wèn)題,解決單點(diǎn)問(wèn)題的方案有:服務(wù)冗余(多一份可用的服務(wù)),做災(zāi)備。
什么是災(zāi)備
災(zāi)備,簡(jiǎn)單點(diǎn)說(shuō),就是生產(chǎn)環(huán)境上部署的服務(wù),假如有一個(gè)服務(wù)(集群)掛了,有另一個(gè)地方的同一個(gè)服務(wù)(集群)可以繼續(xù)使用。
災(zāi)備分主備和雙活兩種部署。假設(shè)有兩個(gè)機(jī)房A、B。
主備:大部分流量都會(huì)到主集群A上,當(dāng)A掛了,備點(diǎn)B能承擔(dān)主集群的角色;
雙活:流量會(huì)平均分配到A、B兩個(gè)機(jī)房,兩個(gè)機(jī)房都能正常對(duì)外服務(wù)。v
如何做一個(gè)合理的災(zāi)備
怎么去做一個(gè)合理的災(zāi)備呢?
筆者結(jié)合自己的工作經(jīng)歷及理論知識(shí),覺(jué)得做災(zāi)備主要是以下的幾點(diǎn),如果還有其他遺漏的,還望各位指正。
一、業(yè)務(wù)梳理
個(gè)人覺(jué)得,對(duì)于業(yè)務(wù)方來(lái)說(shuō),做一個(gè)應(yīng)用的災(zāi)備最重要的一點(diǎn)就是業(yè)務(wù)梳理。理由如下:
1、達(dá)到需要做災(zāi)備的業(yè)務(wù),通常都是存活了有一定時(shí)間的業(yè)務(wù),這些業(yè)務(wù)都會(huì)由于各種因素而有一些在做災(zāi)備時(shí)覺(jué)得不合理的設(shè)計(jì),簡(jiǎn)稱(chēng)歷史原因。這些歷史原因有:依賴(lài)的服務(wù)單點(diǎn);依賴(lài)的數(shù)據(jù)存儲(chǔ)系統(tǒng)單點(diǎn);依賴(lài)的服務(wù)無(wú)法做災(zāi)備等等。這些原因,如果沒(méi)有解決完,那么業(yè)務(wù)方也無(wú)法完成災(zāi)備。
2、不熟悉業(yè)務(wù),對(duì)里面的邏輯不清楚,就不知道如果服務(wù)異常會(huì)導(dǎo)致什么問(wèn)題發(fā)生,貿(mào)然去做災(zāi)備,等到真正有異常時(shí),可能會(huì)發(fā)現(xiàn)沒(méi)有達(dá)到預(yù)期的效果。
業(yè)務(wù)梳理,需要檢查以下幾個(gè)要點(diǎn):
1、業(yè)務(wù)有多少個(gè)依賴(lài)服務(wù)?依賴(lài)服務(wù)是否還有其他的依賴(lài)?
2、依賴(lài)服務(wù)的災(zāi)備情況如何?雙活還是單點(diǎn)?
3、依賴(lài)服務(wù)是否支持重試?重試失敗怎么處理?
4、業(yè)務(wù)使用了什么數(shù)據(jù)存儲(chǔ)系統(tǒng)?部署情況如何?純DB還是有Redis?主從還是多主?是否支持自動(dòng)切換主庫(kù)?
5、業(yè)務(wù)用到的數(shù)據(jù)存儲(chǔ)系統(tǒng)的災(zāi)備情況如何?是否滿(mǎn)足災(zāi)備?是否支持分布式?
6、依賴(lài)的服務(wù)是否可降級(jí)?降級(jí)是否可以返回默認(rèn)值?返回默認(rèn)值對(duì)業(yè)務(wù)是否有損?
7、依賴(lài)服務(wù)多次重試依然失敗,是否可以熔斷?熔斷對(duì)業(yè)務(wù)是否有損?
業(yè)務(wù)梳理完成之后,再根據(jù)對(duì)應(yīng)不滿(mǎn)足的點(diǎn)去完成,直到所有情況都考慮完成了或者使用折中的方案來(lái)解決。
二、 負(fù)載均衡
負(fù)載均衡的意思是將流量負(fù)載分布到多臺(tái)服務(wù)器,從而提高程序的性能和可靠性。通過(guò)負(fù)載均衡技術(shù),可以分發(fā)集中的流量,可以解決兩種情況:
1、流量暴漲,所有流量到一臺(tái)機(jī)器,將應(yīng)用拖垮
2、其中一個(gè)集群的所有應(yīng)用掛了,可以將流量轉(zhuǎn)發(fā)到另一個(gè)集群
注:在筆者實(shí)踐負(fù)載均衡的經(jīng)歷中,使用到最多的就是Nginx的負(fù)載均衡配置,將多個(gè)集群的機(jī)器添加到nginx配置的upstream中,nginx會(huì)根據(jù)配置文件中指定的策略來(lái)分發(fā)流量。
三、服務(wù)降級(jí)
服務(wù)降級(jí):簡(jiǎn)單地說(shuō),就是如果服務(wù)異常,停掉不重要的服務(wù),只返回部分?jǐn)?shù)據(jù)。
比如說(shuō),一個(gè)用戶(hù)信息接口,包含以下三個(gè)字段:
{
"id": 111,
"nickName": "hhq",
"userLogo": "https://www.test.com/test.jpg"
}
如果頭像暫時(shí)獲取失敗,如果返回默認(rèn)頭像用戶(hù)可以接受,那么就降級(jí)返回默認(rèn)的頭像,這樣既不會(huì)使得整個(gè)接口失敗導(dǎo)致無(wú)法進(jìn)行后續(xù)的操作,也不會(huì)影響用戶(hù)體驗(yàn)。
四、服務(wù)熔斷
熔斷:這個(gè)概念參考電路的保險(xiǎn)絲,如果電力負(fù)載過(guò)高,達(dá)到保險(xiǎn)絲熔斷,保險(xiǎn)絲就會(huì)自身熔斷切斷電源,保護(hù)電路安全運(yùn)行。而互聯(lián)網(wǎng)中的服務(wù)熔斷,是指依賴(lài)服務(wù)由于各種因素變得不可用或者響應(yīng)過(guò)慢,業(yè)務(wù)方為了整個(gè)服務(wù)的穩(wěn)定性,不再繼續(xù)調(diào)用目標(biāo)服務(wù),直接返回,如果依賴(lài)服務(wù)恢復(fù)了,則恢復(fù)調(diào)用。
注意,熔斷和降級(jí)看似很相似,但卻是不一樣的概念,應(yīng)該理解為從屬關(guān)系:
1、服務(wù)降級(jí)有多種降級(jí)方式,如限流降級(jí)、熔斷降級(jí)
2、熔斷是降級(jí)的其中一種方式
在熔斷降級(jí)的實(shí)踐中,筆者用到最多的是 Hystrix 。
五、 服務(wù)發(fā)現(xiàn)
服務(wù)發(fā)現(xiàn):自動(dòng)檢測(cè)一個(gè)計(jì)算機(jī)網(wǎng)絡(luò)內(nèi)的設(shè)備機(jī)器提供的服務(wù)。
服務(wù)發(fā)現(xiàn)有一個(gè)服務(wù)中間者維護(hù)服務(wù)與業(yè)務(wù)方之間的關(guān)系,服務(wù)將地址注冊(cè)到服務(wù)中間者,業(yè)務(wù)方從服務(wù)中介中查找需要調(diào)用的服務(wù)的地址。
上面提到,最初開(kāi)始做災(zāi)備是通過(guò)nginx的負(fù)載均衡來(lái)實(shí)現(xiàn),這種方式在服務(wù)部署和擴(kuò)容時(shí)需要修改配置文件,需要自己維護(hù)網(wǎng)絡(luò)中的機(jī)器,一旦不小心配置錯(cuò)誤,整個(gè)服務(wù)就崩了。如果使用服務(wù)發(fā)現(xiàn),由服務(wù)發(fā)現(xiàn)的中介維護(hù)服務(wù)地址,配置時(shí)只需要知道服務(wù)發(fā)現(xiàn)的域名和服務(wù)名稱(chēng)即可,不需要關(guān)心具體的機(jī)器是哪一些。
實(shí)踐過(guò)程中,用到的服務(wù)發(fā)現(xiàn)組件有:Zuul和spring-cloud。
六、演練
如果以上的步驟都完成了,那么就完成災(zāi)備了嗎?并不是的。
現(xiàn)實(shí)情況下,很多時(shí)候是因?yàn)槌霈F(xiàn)了單點(diǎn)故障,才會(huì)想到要去做災(zāi)備。或者其他服務(wù)出現(xiàn)了故障,自身的服務(wù)也要檢查并完成災(zāi)備。那么怎么檢查自己的服務(wù)已經(jīng)完成災(zāi)備了呢?總不可能等待下次的故障到來(lái)才去驗(yàn)證。這種情況下,需要多做服務(wù)的災(zāi)備演練,根據(jù)已做的災(zāi)備要點(diǎn),逐步演練,如果發(fā)現(xiàn)遺漏點(diǎn),重新梳理,繼續(xù)業(yè)務(wù)災(zāi)備,重新演練。不斷循環(huán),直到隨時(shí)演練都能快速恢復(fù)并最小地影響業(yè)務(wù)或者業(yè)務(wù)完全無(wú)感知才算完成了災(zāi)備。
踩過(guò)的坑
以上的這些理論是多次反復(fù)實(shí)踐得出的總結(jié),以筆者自己做災(zāi)備的經(jīng)歷,給大家分享我遇到過(guò)的兩個(gè)比較大的坑。
真的只是重試就完了嗎?流量暴漲,拖垮服務(wù)
接口A,依賴(lài)服務(wù)B,B依賴(lài)服務(wù)C,部署情況如下:

當(dāng)時(shí)做了雙活+網(wǎng)關(guān)重試+負(fù)載均衡的部署,出現(xiàn)的情況是B->C超時(shí),導(dǎo)致A接口響應(yīng)太慢,這里B->C有兩次重試,A->B也有兩次重試,接口超時(shí)時(shí)間太長(zhǎng),網(wǎng)關(guān)判斷接口失敗,于是也做了兩次重試,最終的結(jié)果是,同一個(gè)接口,有 2*2*2=8 倍的流量,導(dǎo)致服務(wù)C的請(qǐng)求量暴漲,于是將服務(wù)C的進(jìn)程池耗盡,服務(wù)499了,最終接口A一直都是失敗,直到B->C之間的網(wǎng)絡(luò)恢復(fù)才正常。
這次的故障得出的結(jié)論是:
1、重試不能單純加上就完事了,需要看下游的依賴(lài)是否滿(mǎn)足重試
2、重試多次失敗后就需要加熔斷降級(jí)
3、重要的接口,除了重試以外,還可以做部分?jǐn)?shù)據(jù)降級(jí)提高接口高可用性
機(jī)房服務(wù)“孤島”
有接口A,B、C兩個(gè)服務(wù),A-B之間通過(guò)外網(wǎng)相連,B-C之間通過(guò)內(nèi)網(wǎng)相連。異常情況是B-C之間網(wǎng)絡(luò)不通,外網(wǎng)流量通過(guò)接口A進(jìn)入到B,B依賴(lài)C,但是B-C之間不通,B調(diào)用C會(huì)不斷重試,直到全部重試都失敗了,才會(huì)返回網(wǎng)絡(luò)錯(cuò)誤。這樣一來(lái),接口A并不知道B服務(wù)失敗,用戶(hù)側(cè)體驗(yàn)是一直等待,然后顯示失敗。理想的做法是希望能在B-C網(wǎng)絡(luò)不通的情況下將后續(xù)到來(lái)的流量拒絕掉,快速響應(yīng)失敗的結(jié)果。

要做到這一點(diǎn),就需要讓服務(wù)B“自殺”,如果應(yīng)用側(cè)發(fā)現(xiàn)B-C之間的網(wǎng)絡(luò)出現(xiàn)異常,就讓B返回失敗錯(cuò)誤碼,不再進(jìn)行重試。
需要注意的點(diǎn)
不能脫離業(yè)務(wù)
眾所周知,開(kāi)發(fā)大部分的時(shí)間都需要趕需求,一方面需求多到無(wú)法擠出時(shí)間完成災(zāi)備的任務(wù),另一方面災(zāi)備工作如果不完成,出現(xiàn)故障之后就會(huì)影響業(yè)務(wù)了。因此通常會(huì)將這類(lèi)需求當(dāng)作技術(shù)需求來(lái)完成,業(yè)務(wù)開(kāi)發(fā)人員沒(méi)有時(shí)間完成災(zāi)備工作時(shí),就會(huì)讓一些負(fù)責(zé)技術(shù)需求的開(kāi)發(fā)來(lái)直接完成災(zāi)備。上面提到,完成災(zāi)備最重要的一點(diǎn)就是需要梳理業(yè)務(wù),如果由一個(gè)完成不懂該業(yè)務(wù)的開(kāi)發(fā)去完成災(zāi)備,那么至少需要花1-2天去閱讀代碼,理清業(yè)務(wù)邏輯和列出可能出現(xiàn)的坑才能完成這份工作。但是筆者覺(jué)得這樣做的效率是比較低的。首先,人無(wú)完人,雖然代碼大家都能看懂,但是由一個(gè)未參與過(guò)業(yè)務(wù)的開(kāi)發(fā)重新梳理,難免會(huì)有遺漏的地方(即使問(wèn)相應(yīng)的業(yè)務(wù)開(kāi)發(fā),也有可能會(huì)遺漏);其次,重新熟悉業(yè)務(wù)也需要投入一定的時(shí)間。
所以,做災(zāi)備是不能脫離業(yè)務(wù)的,應(yīng)該給業(yè)務(wù)開(kāi)發(fā)勻出相應(yīng)的時(shí)間完成服務(wù)災(zāi)備,提高服務(wù)穩(wěn)定性,這對(duì)業(yè)務(wù)而言,出現(xiàn)故障時(shí)不影響用戶(hù)的使用,用戶(hù)無(wú)感知,就是提高用戶(hù)的體驗(yàn)。
自動(dòng)化運(yùn)維
在筆者的災(zāi)備經(jīng)歷中,如果機(jī)器出現(xiàn)故障/機(jī)房故障/流量暴漲,都需要運(yùn)維和相應(yīng)的業(yè)務(wù)開(kāi)發(fā)人工介入判斷是否需要擴(kuò)容或摘除機(jī)器。但是人的判讀是主觀的,對(duì)是否需要擴(kuò)容以及機(jī)器的選擇可能會(huì)有判斷出錯(cuò)的時(shí)候,災(zāi)備的工作,如果能結(jié)合現(xiàn)在較成熟k8s進(jìn)行自動(dòng)化運(yùn)維,那么將達(dá)到事半功倍的效果。
總結(jié)
災(zāi)備,對(duì)于服務(wù)穩(wěn)定性而言十分重要,但是也不是一朝一夕能完成的。個(gè)人覺(jué)得核心要點(diǎn)就是盡最大努力消除單點(diǎn)故障:服務(wù)單點(diǎn)、數(shù)據(jù)系統(tǒng)單點(diǎn)等等。
以上的文字理論僅僅是筆者經(jīng)歷過(guò)的小小總結(jié),也許仍未做到最好的災(zāi)備級(jí)別,還需要日后不斷實(shí)踐來(lái)提升這部分的知識(shí),如果有說(shuō)錯(cuò)的地方,還望各位指正。