更新時間:2019-10-25 15:54:51 來源:動力節(jié)點 瀏覽2862次
對于企業(yè)來說,隨著規(guī)模越來越大,整個系統(tǒng)中存在越來越多的子系統(tǒng),每個子系統(tǒng)又被多個其他子系統(tǒng)依賴或者依賴于其他子系統(tǒng)。大部分系統(tǒng)在走到這一步的過程中,大概率會發(fā)生這樣的場景:作為某個子系統(tǒng)的負責人或者OnCall人員,休息的時候都不安穩(wěn),心里老是忐忑著系統(tǒng)會不會掛。導(dǎo)致周末不敢長時間出門,晚上睡夢中被電話叫醒,痛苦不堪。
那么,在一個成熟的分布式系統(tǒng)中,我們該如何去保證它的可用性呢?迫切的需要解放我們緊繃的神經(jīng)。下面,我們就來看下做高可用的思路和關(guān)鍵部分。
如何下手做高可用?
在這個時候,我們的系統(tǒng)全貌大致是這樣的。
由大大小小的多個部分組合而成的一個完整系統(tǒng),可以看到包含網(wǎng)關(guān)、Web層、服務(wù)層、中間件、基礎(chǔ)設(shè)施,這每一層之間又是層層依賴。在如此的一個龐然大物面前做高可用是一個系統(tǒng)化的工程,除了良好的頂層設(shè)計規(guī)劃外,還需要深入到細節(jié)。由于雪崩效應(yīng)的存在,軟件系統(tǒng)是一個完美體現(xiàn)“千里之堤毀于蟻穴”的地方,一個小問題導(dǎo)致整個系統(tǒng)全盤崩塌的案例也不在少數(shù)。
所以,首先我們需要擁有保持懷疑的心態(tài)。這個懷疑是指對系統(tǒng)的懷疑,而不是對人的懷疑。人非圣賢孰能無過,況且寫代碼是一個精細活,還不是流水線式的那種。而且,哪怕不是寫代碼的疏忽,其他諸如網(wǎng)絡(luò)、操作系統(tǒng)等異常,甚至一些惡意的攻擊都會導(dǎo)致故障隨時發(fā)生。
那么我們具體應(yīng)該怎么做呢?既然故障導(dǎo)致了可用性降低,那么接下來的工作必然是圍繞解決故障展開。分為3個步驟:故障發(fā)現(xiàn)、故障消除、故障善后。
故障發(fā)現(xiàn)
所謂“故障發(fā)現(xiàn)”,就是通過技術(shù)手段實時采集系統(tǒng)中每個節(jié)點的健康狀態(tài),以及每2個節(jié)點之間鏈路的健康狀態(tài),包括但不限于調(diào)用成功率、響應(yīng)時間等等。借此代替我們的眼睛去盯著整個系統(tǒng),一旦低于某個設(shè)定的閾值,就觸發(fā)報警給我們一個提醒。因為當你的系統(tǒng)中存在成百上千的程序時,靠肉眼去找到發(fā)生故障的位置,簡直是天方夜譚。哪怕找到了,也可能已經(jīng)產(chǎn)生了巨大的損失。
負責故障發(fā)現(xiàn)的解決方案都屬于應(yīng)用性能管理(APM)范疇。我們在部署這個“眼睛”的時候,需要考慮到全方位的覆蓋,要包含所有的節(jié)點。比如:
在Web方面可以直接利用瀏覽器提供的導(dǎo)航計時(NavigationTiming)和資源計時(ResourceTiming)接口來采集性能數(shù)據(jù),非常方便。
在iOS、Android這種App方面通過源代碼插樁的方式進行。比如直接引入采集SDK然后硬編碼在源代碼中,或者通過AOP框架來進行動態(tài)代碼注入。代碼的注入位置就在每個方法的執(zhí)行前和執(zhí)行后(如下圖所示)。

后端是分布式系統(tǒng)的主戰(zhàn)場,有進程外和進程內(nèi)兩個維度的解決方案。
1)進程外的解決方案,例如運用Zabbix之類的無探針解決方案,調(diào)用系統(tǒng)或者服務(wù)自身提供的狀態(tài)接口獲取采集數(shù)據(jù)(如下圖所示),以及對網(wǎng)絡(luò)數(shù)據(jù)包的監(jiān)聽來獲取網(wǎng)絡(luò)性能方面的數(shù)據(jù)。

由于是進程外的,所以這類方案對我們的程序是無侵入的,最友好。但弊端也很明顯,監(jiān)控的粒度太粗,只能進行一些外在的監(jiān)控。比如可以發(fā)現(xiàn)CPU突然飆高了,但是并不知道可疑的接口是哪個,更無法知道是哪行代碼導(dǎo)致的問題。因此,只適合作為輔助方案。
2)后端的進程內(nèi)解決方案可以解決進程外方案的短板,但是由于需要侵入到應(yīng)用程序內(nèi)部,所以對性能和穩(wěn)定性會帶來一定的影響。關(guān)于這類方案我們有很多的選擇可以來實現(xiàn)它:可以同APP一樣運用采集SDK和AOP框架,還可以通過利用整個系統(tǒng)中的“連接”部分來進行,比如一些中間件(數(shù)據(jù)層訪問框架、服務(wù)調(diào)用框架等)。

做好了監(jiān)控,就做好了故障發(fā)現(xiàn)一半的工作。另外一半是什么呢?就是故障注入測試(FaultInsertionTest)。我們需要通過技術(shù)手段來主動制造“故障”,以此來提前檢驗系統(tǒng)在各種故障場景下的表現(xiàn)情況是否符合我們預(yù)期。
監(jiān)控是一雙眼睛,替你盯著故障,但是我們不能守株待兔,否則大部分突發(fā)的故障都會在生產(chǎn)環(huán)境發(fā)生。一旦發(fā)生就會對經(jīng)營的業(yè)務(wù)產(chǎn)生或多或少的影響,甚至看似平靜的系統(tǒng)下,藏著幾個隨時會引爆的炸彈,我們也不得而知。所以我們需要主動出擊,主動去制造“故障”來鍛煉系統(tǒng)。
在實際運用中,故障可以被注入到軟件,也可以被注入到硬件。注入到軟件的方式,無外乎這兩種:
架設(shè)在軟件與操作系統(tǒng)之間,當軟件中的數(shù)據(jù)經(jīng)過操作系統(tǒng)時,通過篡改數(shù)據(jù)完成注入。
通過AOP之類的框架進行代碼注入來制造故障。
如果注入到硬件中就簡單很多,直接運行一段代碼把CPU、網(wǎng)卡等吃滿即可。
故障注入測試的過程大致是這樣,在故障模型庫中選擇一個模型,然后將該模型對應(yīng)的故障注入到一個在獨立的環(huán)境中運行并且被包裹了一層“炸藥包”的系統(tǒng),相當于在你指定的地方去“點火”,隨后進行監(jiān)測并分析結(jié)果(如下圖所示)。

故障消除
現(xiàn)在已經(jīng)能夠很容易的發(fā)現(xiàn)故障了,我們就可以通過綜合運用隔離性、橫向擴展、代理、負載均衡、熔斷、限流、降級等等機制來快速的“掐滅故障”。
分布式系統(tǒng)的規(guī)模越大,耦合越嚴重,各個子系統(tǒng)之間通過網(wǎng)絡(luò)連接在一起,就如赤壁之戰(zhàn)中的曹軍連在一起的船舶一樣,只要其中一個著火了就會就近蔓延。所以,一旦發(fā)現(xiàn)某個子系統(tǒng)掛了,就需要盡快切斷與它的聯(lián)系,保證自己能夠不受連累,防止雪崩的發(fā)生。
我們可以首先運用docker之類的技術(shù)將每個應(yīng)用在運行時的環(huán)境層面隔離開來。然后,通過橫向擴展讓每個應(yīng)用允許被“Copy”,以此來部署多個副本。接著,結(jié)合代理和負載均衡讓這些副本可以共同對外提供服務(wù),使得每個應(yīng)用程序本身先具備“高可用”。最后的三大防御措施,熔斷、限流、降級來快速“掐滅故障”,避免故障在不同的應(yīng)用程序間擴散。
故障善后
“故障消除”避免了級聯(lián)故障導(dǎo)致的系統(tǒng)性風險,這時整個分布式系統(tǒng)已經(jīng)具備健壯性了。但是對正在使用系統(tǒng)的用戶來說,這些故障還是可見的,因為會反映成他實際操作中的錯誤提示,甚至導(dǎo)致流程無法繼續(xù)。這對我們“衣食父母”來說并不友好,最終可能會導(dǎo)致用戶的流失。
所以,我們應(yīng)該通過一些補償和緩沖的方式將故障產(chǎn)生的影響降到最低,盡可能的去包容故障,讓用戶無感。并且,這些善后工作應(yīng)該與“故障發(fā)現(xiàn)”、“故障消除”一起形成一個完整的體系,以及盡可能的自動化。
前面我們聊到,故障產(chǎn)生的原因要么是調(diào)用的節(jié)點處于異常狀態(tài),要么是通信鏈路異常。所以,要做好“故障善后”,就需要在節(jié)點之間的連接上做文章。根據(jù)CAP定理、BASE理論,我們已經(jīng)很清楚兩個進程之間的調(diào)用方式。一是直接點對點的同步調(diào)用,或者是通過一些技術(shù)中間層進行異步的調(diào)用。
那么,針對同步調(diào)用我們可以有兩種方式去實施。
首先是立即重試。很多時候,相同節(jié)點的所有副本可能只是由于網(wǎng)絡(luò)原因,導(dǎo)致其中的某個節(jié)點無法被訪問。那么,此時如果后端的負載均衡策略只要不是Hash類的策略,并且后端服務(wù)的方法是無狀態(tài)的且支持冪等性的,就可以立馬重試一次,大概率就能調(diào)用成功。不過,這個方案潛在的一個副作用是,如果后端服務(wù)總體負載很高,且無法自動彈性擴容,那么會進一步加劇一些壓力。所以,你可以增加一個允許被重試的條件,以及為實際的重試操作增加一個約定。比如,這兩個耗時分別都不能大于1秒。
方式二,將可以容忍最終一致性的同步調(diào)用產(chǎn)生的出錯消息進行異步重發(fā)。比如,電商網(wǎng)站中提交訂單中所依賴的訂單模塊產(chǎn)生故障,我們可以將其暫存到消息隊列中,然后再進行異步的投遞,同時提示給用戶“訂單正在加緊創(chuàng)建中,稍后通知您支付”之類的語句,至少先讓訂單能夠下進來。這本質(zhì)上算得是一個“降級”方案。
如果本身就是一個異步調(diào)用,比如最常見的就是發(fā)往消息隊列出現(xiàn)異常。因為,一個高可用的消息隊列集群,大多數(shù)情況下導(dǎo)致消息無法被投遞的原因是網(wǎng)絡(luò)問題。這個時候,理論上我們可以基于每個應(yīng)用的本地磁盤部署一個本地MQ,可以避免很大一部分這個問題。但是實際往往不會這么做,因為這么做的性價比太低,原因有兩點:
這么多消息隊列維護成本太高。
如果用到的是消息隊列集群,本身已具備軟件層面的高可用,所以出現(xiàn)這個問題的概率很低。
所以,這個時候我們大多會通過定時的任務(wù)(job)去進行對賬(數(shù)據(jù)一致性檢測)。任務(wù)(job)的具體實現(xiàn)上盡可能做到自動修正,否則通知人工介入。
以上就是動力節(jié)點java培訓(xùn)機構(gòu)小編介紹的“Java架構(gòu)師教程:分布式系統(tǒng)中如何下手做高可用”的內(nèi)容,希望對大家有幫助,如有疑問,請在線咨詢,有專業(yè)老師隨時為你服務(wù)。
相關(guān)內(nèi)容
做一名高級Java架構(gòu)師,學(xué)Java架構(gòu)師開發(fā)難嗎
【java架構(gòu)師培訓(xùn)】合格java架構(gòu)師標準是什么
2019最新Java架構(gòu)師學(xué)習(xí)路線
相關(guān)閱讀