對(duì)于企業(yè)來(lái)說(shuō),隨著規(guī)模越來(lái)越大,整個(gè)系統(tǒng)中存在越來(lái)越多的子系統(tǒng),每個(gè)子系統(tǒng)又被多個(gè)其他子系統(tǒng)依賴或者依賴于其他子系統(tǒng)。大部分系統(tǒng)在走到這一步的過(guò)程中,大概率會(huì)發(fā)生這樣的場(chǎng)景:作為某個(gè)子系統(tǒng)的負(fù)責(zé)人或者OnCall人員,休息的時(shí)候都不安穩(wěn),心里老是忐忑著系統(tǒng)會(huì)不會(huì)掛。導(dǎo)致周末不敢長(zhǎng)時(shí)間出門(mén),晚上睡夢(mèng)中被電話叫醒,痛苦不堪。
那么,在一個(gè)成熟的分布式系統(tǒng)中,我們?cè)撊绾稳ケWC它的可用性呢?迫切的需要解放我們緊繃的神經(jīng)。下面,我們就來(lái)看下做高可用的思路和關(guān)鍵部分。
如何下手做高可用?
在這個(gè)時(shí)候,我們的系統(tǒng)全貌大致是這樣的。
由大大小小的多個(gè)部分組合而成的一個(gè)完整系統(tǒng),可以看到包含網(wǎng)關(guān)、Web層、服務(wù)層、中間件、基礎(chǔ)設(shè)施,這每一層之間又是層層依賴。在如此的一個(gè)龐然大物面前做高可用是一個(gè)系統(tǒng)化的工程,除了良好的頂層設(shè)計(jì)規(guī)劃外,還需要深入到細(xì)節(jié)。由于雪崩效應(yīng)的存在,軟件系統(tǒng)是一個(gè)完美體現(xiàn)“千里之堤毀于蟻穴”的地方,一個(gè)小問(wèn)題導(dǎo)致整個(gè)系統(tǒng)全盤(pán)崩塌的案例也不在少數(shù)。
所以,首先我們需要擁有保持懷疑的心態(tài)。這個(gè)懷疑是指對(duì)系統(tǒng)的懷疑,而不是對(duì)人的懷疑。人非圣賢孰能無(wú)過(guò),況且寫(xiě)代碼是一個(gè)精細(xì)活,還不是流水線式的那種。而且,哪怕不是寫(xiě)代碼的疏忽,其他諸如網(wǎng)絡(luò)、操作系統(tǒng)等異常,甚至一些惡意的攻擊都會(huì)導(dǎo)致故障隨時(shí)發(fā)生。
那么我們具體應(yīng)該怎么做呢?既然故障導(dǎo)致了可用性降低,那么接下來(lái)的工作必然是圍繞解決故障展開(kāi)。分為3個(gè)步驟:故障發(fā)現(xiàn)、故障消除、故障善后。
故障發(fā)現(xiàn)
所謂“故障發(fā)現(xiàn)”,就是通過(guò)技術(shù)手段實(shí)時(shí)采集系統(tǒng)中每個(gè)節(jié)點(diǎn)的健康狀態(tài),以及每2個(gè)節(jié)點(diǎn)之間鏈路的健康狀態(tài),包括但不限于調(diào)用成功率、響應(yīng)時(shí)間等等。借此代替我們的眼睛去盯著整個(gè)系統(tǒng),一旦低于某個(gè)設(shè)定的閾值,就觸發(fā)報(bào)警給我們一個(gè)提醒。因?yàn)楫?dāng)你的系統(tǒng)中存在成百上千的程序時(shí),靠肉眼去找到發(fā)生故障的位置,簡(jiǎn)直是天方夜譚。哪怕找到了,也可能已經(jīng)產(chǎn)生了巨大的損失。
負(fù)責(zé)故障發(fā)現(xiàn)的解決方案都屬于應(yīng)用性能管理(APM)范疇。我們?cè)诓渴疬@個(gè)“眼睛”的時(shí)候,需要考慮到全方位的覆蓋,要包含所有的節(jié)點(diǎn)。比如:
在Web方面可以直接利用瀏覽器提供的導(dǎo)航計(jì)時(shí)(NavigationTiming)和資源計(jì)時(shí)(ResourceTiming)接口來(lái)采集性能數(shù)據(jù),非常方便。
在iOS、Android這種App方面通過(guò)源代碼插樁的方式進(jìn)行。比如直接引入采集SDK然后硬編碼在源代碼中,或者通過(guò)AOP框架來(lái)進(jìn)行動(dòng)態(tài)代碼注入。代碼的注入位置就在每個(gè)方法的執(zhí)行前和執(zhí)行后(如下圖所示)。
后端是分布式系統(tǒng)的主戰(zhàn)場(chǎng),有進(jìn)程外和進(jìn)程內(nèi)兩個(gè)維度的解決方案。
1)進(jìn)程外的解決方案,例如運(yùn)用Zabbix之類(lèi)的無(wú)探針解決方案,調(diào)用系統(tǒng)或者服務(wù)自身提供的狀態(tài)接口獲取采集數(shù)據(jù)(如下圖所示),以及對(duì)網(wǎng)絡(luò)數(shù)據(jù)包的監(jiān)聽(tīng)來(lái)獲取網(wǎng)絡(luò)性能方面的數(shù)據(jù)。
由于是進(jìn)程外的,所以這類(lèi)方案對(duì)我們的程序是無(wú)侵入的,最友好。但弊端也很明顯,監(jiān)控的粒度太粗,只能進(jìn)行一些外在的監(jiān)控。比如可以發(fā)現(xiàn)CPU突然飆高了,但是并不知道可疑的接口是哪個(gè),更無(wú)法知道是哪行代碼導(dǎo)致的問(wèn)題。因此,只適合作為輔助方案。
2)后端的進(jìn)程內(nèi)解決方案可以解決進(jìn)程外方案的短板,但是由于需要侵入到應(yīng)用程序內(nèi)部,所以對(duì)性能和穩(wěn)定性會(huì)帶來(lái)一定的影響。關(guān)于這類(lèi)方案我們有很多的選擇可以來(lái)實(shí)現(xiàn)它:可以同APP一樣運(yùn)用采集SDK和AOP框架,還可以通過(guò)利用整個(gè)系統(tǒng)中的“連接”部分來(lái)進(jìn)行,比如一些中間件(數(shù)據(jù)層訪問(wèn)框架、服務(wù)調(diào)用框架等)。
做好了監(jiān)控,就做好了故障發(fā)現(xiàn)一半的工作。另外一半是什么呢?就是故障注入測(cè)試(FaultInsertionTest)。我們需要通過(guò)技術(shù)手段來(lái)主動(dòng)制造“故障”,以此來(lái)提前檢驗(yàn)系統(tǒng)在各種故障場(chǎng)景下的表現(xiàn)情況是否符合我們預(yù)期。
監(jiān)控是一雙眼睛,替你盯著故障,但是我們不能守株待兔,否則大部分突發(fā)的故障都會(huì)在生產(chǎn)環(huán)境發(fā)生。一旦發(fā)生就會(huì)對(duì)經(jīng)營(yíng)的業(yè)務(wù)產(chǎn)生或多或少的影響,甚至看似平靜的系統(tǒng)下,藏著幾個(gè)隨時(shí)會(huì)引爆的炸彈,我們也不得而知。所以我們需要主動(dòng)出擊,主動(dòng)去制造“故障”來(lái)鍛煉系統(tǒng)。
在實(shí)際運(yùn)用中,故障可以被注入到軟件,也可以被注入到硬件。注入到軟件的方式,無(wú)外乎這兩種:
架設(shè)在軟件與操作系統(tǒng)之間,當(dāng)軟件中的數(shù)據(jù)經(jīng)過(guò)操作系統(tǒng)時(shí),通過(guò)篡改數(shù)據(jù)完成注入。
通過(guò)AOP之類(lèi)的框架進(jìn)行代碼注入來(lái)制造故障。
如果注入到硬件中就簡(jiǎn)單很多,直接運(yùn)行一段代碼把CPU、網(wǎng)卡等吃滿即可。
故障注入測(cè)試的過(guò)程大致是這樣,在故障模型庫(kù)中選擇一個(gè)模型,然后將該模型對(duì)應(yīng)的故障注入到一個(gè)在獨(dú)立的環(huán)境中運(yùn)行并且被包裹了一層“炸藥包”的系統(tǒng),相當(dāng)于在你指定的地方去“點(diǎn)火”,隨后進(jìn)行監(jiān)測(cè)并分析結(jié)果(如下圖所示)。
故障消除
現(xiàn)在已經(jīng)能夠很容易的發(fā)現(xiàn)故障了,我們就可以通過(guò)綜合運(yùn)用隔離性、橫向擴(kuò)展、代理、負(fù)載均衡、熔斷、限流、降級(jí)等等機(jī)制來(lái)快速的“掐滅故障”。
分布式系統(tǒng)的規(guī)模越大,耦合越嚴(yán)重,各個(gè)子系統(tǒng)之間通過(guò)網(wǎng)絡(luò)連接在一起,就如赤壁之戰(zhàn)中的曹軍連在一起的船舶一樣,只要其中一個(gè)著火了就會(huì)就近蔓延。所以,一旦發(fā)現(xiàn)某個(gè)子系統(tǒng)掛了,就需要盡快切斷與它的聯(lián)系,保證自己能夠不受連累,防止雪崩的發(fā)生。
我們可以首先運(yùn)用docker之類(lèi)的技術(shù)將每個(gè)應(yīng)用在運(yùn)行時(shí)的環(huán)境層面隔離開(kāi)來(lái)。然后,通過(guò)橫向擴(kuò)展讓每個(gè)應(yīng)用允許被“Copy”,以此來(lái)部署多個(gè)副本。接著,結(jié)合代理和負(fù)載均衡讓這些副本可以共同對(duì)外提供服務(wù),使得每個(gè)應(yīng)用程序本身先具備“高可用”。最后的三大防御措施,熔斷、限流、降級(jí)來(lái)快速“掐滅故障”,避免故障在不同的應(yīng)用程序間擴(kuò)散。
故障善后
“故障消除”避免了級(jí)聯(lián)故障導(dǎo)致的系統(tǒng)性風(fēng)險(xiǎn),這時(shí)整個(gè)分布式系統(tǒng)已經(jīng)具備健壯性了。但是對(duì)正在使用系統(tǒng)的用戶來(lái)說(shuō),這些故障還是可見(jiàn)的,因?yàn)闀?huì)反映成他實(shí)際操作中的錯(cuò)誤提示,甚至導(dǎo)致流程無(wú)法繼續(xù)。這對(duì)我們“衣食父母”來(lái)說(shuō)并不友好,最終可能會(huì)導(dǎo)致用戶的流失。
所以,我們應(yīng)該通過(guò)一些補(bǔ)償和緩沖的方式將故障產(chǎn)生的影響降到最低,盡可能的去包容故障,讓用戶無(wú)感。并且,這些善后工作應(yīng)該與“故障發(fā)現(xiàn)”、“故障消除”一起形成一個(gè)完整的體系,以及盡可能的自動(dòng)化。
前面我們聊到,故障產(chǎn)生的原因要么是調(diào)用的節(jié)點(diǎn)處于異常狀態(tài),要么是通信鏈路異常。所以,要做好“故障善后”,就需要在節(jié)點(diǎn)之間的連接上做文章。根據(jù)CAP定理、base理論,我們已經(jīng)很清楚兩個(gè)進(jìn)程之間的調(diào)用方式。一是直接點(diǎn)對(duì)點(diǎn)的同步調(diào)用,或者是通過(guò)一些技術(shù)中間層進(jìn)行異步的調(diào)用。
那么,針對(duì)同步調(diào)用我們可以有兩種方式去實(shí)施。
首先是立即重試。很多時(shí)候,相同節(jié)點(diǎn)的所有副本可能只是由于網(wǎng)絡(luò)原因,導(dǎo)致其中的某個(gè)節(jié)點(diǎn)無(wú)法被訪問(wèn)。那么,此時(shí)如果后端的負(fù)載均衡策略只要不是Hash類(lèi)的策略,并且后端服務(wù)的方法是無(wú)狀態(tài)的且支持冪等性的,就可以立馬重試一次,大概率就能調(diào)用成功。不過(guò),這個(gè)方案潛在的一個(gè)副作用是,如果后端服務(wù)總體負(fù)載很高,且無(wú)法自動(dòng)彈性擴(kuò)容,那么會(huì)進(jìn)一步加劇一些壓力。所以,你可以增加一個(gè)允許被重試的條件,以及為實(shí)際的重試操作增加一個(gè)約定。比如,這兩個(gè)耗時(shí)分別都不能大于1秒。
方式二,將可以容忍最終一致性的同步調(diào)用產(chǎn)生的出錯(cuò)消息進(jìn)行異步重發(fā)。比如,電商網(wǎng)站中提交訂單中所依賴的訂單模塊產(chǎn)生故障,我們可以將其暫存到消息隊(duì)列中,然后再進(jìn)行異步的投遞,同時(shí)提示給用戶“訂單正在加緊創(chuàng)建中,稍后通知您支付”之類(lèi)的語(yǔ)句,至少先讓訂單能夠下進(jìn)來(lái)。這本質(zhì)上算得是一個(gè)“降級(jí)”方案。
如果本身就是一個(gè)異步調(diào)用,比如最常見(jiàn)的就是發(fā)往消息隊(duì)列出現(xiàn)異常。因?yàn)?,一個(gè)高可用的消息隊(duì)列集群,大多數(shù)情況下導(dǎo)致消息無(wú)法被投遞的原因是網(wǎng)絡(luò)問(wèn)題。這個(gè)時(shí)候,理論上我們可以基于每個(gè)應(yīng)用的本地磁盤(pán)部署一個(gè)本地MQ,可以避免很大一部分這個(gè)問(wèn)題。但是實(shí)際往往不會(huì)這么做,因?yàn)檫@么做的性價(jià)比太低,原因有兩點(diǎn):
這么多消息隊(duì)列維護(hù)成本太高。
如果用到的是消息隊(duì)列集群,本身已具備軟件層面的高可用,所以出現(xiàn)這個(gè)問(wèn)題的概率很低。
所以,這個(gè)時(shí)候我們大多會(huì)通過(guò)定時(shí)的任務(wù)(job)去進(jìn)行對(duì)賬(數(shù)據(jù)一致性檢測(cè))。任務(wù)(job)的具體實(shí)現(xiàn)上盡可能做到自動(dòng)修正,否則通知人工介入。
以上就是長(zhǎng)沙中公優(yōu)就業(yè)java培訓(xùn)機(jī)構(gòu)小編介紹的“Java架構(gòu)師教程:分布式系統(tǒng)中如何下手做高可用”的內(nèi)容,希望對(duì)大家有幫助,如有疑問(wèn),請(qǐng)?jiān)诰€咨詢,有專(zhuān)業(yè)老師隨時(shí)為你服務(wù)。
相關(guān)內(nèi)容
做一名高級(jí)Java架構(gòu)師,學(xué)Java架構(gòu)師開(kāi)發(fā)難嗎
【java架構(gòu)師培訓(xùn)】合格java架構(gòu)師標(biāo)準(zhǔn)是什么
2019最新Java架構(gòu)師學(xué)習(xí)路線
Java架構(gòu)師年薪一般多少?