高可用系统架构设计 技术方案
背景
可靠的系統(tǒng)是業(yè)務(wù)穩(wěn)定、快速發(fā)展的基石。
那么,如何做到系統(tǒng)高可靠、高可用呢?
高可用方法論
下面的表格里,列出了高可用常見的問題和應(yīng)對措施。
可擴(kuò)展
擴(kuò)展是最常見的提升系統(tǒng)可靠性的方法,系統(tǒng)的擴(kuò)展可以避免單點故障,即一個節(jié)點出現(xiàn)了問題造成整個系統(tǒng)無法正常工作。
換一個角度講,一個容易擴(kuò)展的系統(tǒng),能夠通過擴(kuò)展來成倍的提升系統(tǒng)能力,輕松應(yīng)對系統(tǒng)訪問量的提升。
一般地,擴(kuò)展可以分為垂直擴(kuò)展和水平擴(kuò)展:
1、垂直擴(kuò)展
在同一邏輯單元里添加資源從而滿足系統(tǒng)處理能力上升的需求。
比如,當(dāng)機(jī)器內(nèi)存不夠時,我們可以幫機(jī)器增加內(nèi)存,或者數(shù)據(jù)存不下時,我們?yōu)闄C(jī)器掛載新的磁盤。
垂直擴(kuò)展能夠提升系統(tǒng)處理能力,但不能解決單點故障問題。
優(yōu)點:擴(kuò)展簡單。
缺點:擴(kuò)展能力有限。
2、水平擴(kuò)展
通過增加一個或多個邏輯單元,并使得它們像整體一樣的工作。
水平擴(kuò)展,通過冗余部署解決了單點故障,同時又提升了系統(tǒng)處理能力。
優(yōu)點:擴(kuò)展能力強(qiáng)。
缺點:增加系統(tǒng)復(fù)雜度,維護(hù)成本高,系統(tǒng)需要是無狀態(tài)的、可分布式的。
可擴(kuò)展性系數(shù) scalability factor 通常用來衡量一個系統(tǒng)的擴(kuò)展能力,當(dāng)增加 1 單元的資源時,系統(tǒng)處理能力只增加了 0.95 單元,那么可擴(kuò)展性系數(shù)就是 95%。當(dāng)系統(tǒng)在持續(xù)的擴(kuò)展中,可擴(kuò)展系數(shù)始終保持不變,我們就稱這種擴(kuò)展是線性可擴(kuò)展。
在實際應(yīng)用中,水平擴(kuò)展最常見:
1)通常我們在部署應(yīng)用服務(wù)器的時候,都會部署多臺,然后使用 nginx 來做負(fù)載均衡,nginx 使用心跳機(jī)制來檢測服務(wù)器的正常與否,無響應(yīng)的服務(wù)就從集群中剔除。這樣的集群中每臺服務(wù)器的角色是相同的,同時提供一樣的服務(wù)。
2)在數(shù)據(jù)庫的部署中,為了防止單點故障,一般會使用一主多從,通常寫操作只發(fā)生在主庫。不同數(shù)據(jù)庫之間角色不同。當(dāng)主機(jī)宕機(jī)時,一臺從庫可以自動切換為主機(jī)提供服務(wù)。
可隔離
隔離,是對什么進(jìn)行隔離呢?是對系統(tǒng)、業(yè)務(wù)所占有的資源進(jìn)行隔離,限制某個業(yè)務(wù)對資源的占用數(shù)量,避免一個業(yè)務(wù)占用整個系統(tǒng)資源,對其他業(yè)務(wù)造成影響。
隔離級別按粒度從小到大,可以分為線程池隔離、進(jìn)程隔離、模塊隔離、應(yīng)用隔離、機(jī)房隔離。在數(shù)據(jù)庫的使用中,還經(jīng)常用到讀寫分離。
1、線程池隔離
不同的業(yè)務(wù)使用不同的線程池,避免低優(yōu)先級的任務(wù)阻塞高優(yōu)先級的任務(wù)。或者高優(yōu)先級的任務(wù)過多,導(dǎo)致低優(yōu)先級任務(wù)永遠(yuǎn)不會執(zhí)行。
2、進(jìn)程隔離
Linux 中有用于進(jìn)程資源隔離的 Linux CGroup,通過物理限制的方式為進(jìn)程間資源控制提供了簡單的實現(xiàn)方式,為 Linux Container 技術(shù)、虛擬化技術(shù)的發(fā)展奠定了技術(shù)基礎(chǔ)
3、模塊隔離、應(yīng)用隔離
很多線上故障的發(fā)生源于代碼修改后,測試不到位導(dǎo)致。按照代碼或業(yè)務(wù)的易變程度來劃分模塊或應(yīng)用,把變化較少的劃分到一個模塊或應(yīng)用中,變化較多的劃分到另一個模塊或應(yīng)用中。減少代碼修改影響的范圍,也就減少了測試的工作量,減少了故障出現(xiàn)的概率。
4、機(jī)房隔離
主要是為了避免單個機(jī)房網(wǎng)絡(luò)問題或斷電吧。
5、讀寫分離
一方面,將對實時性要求不高的讀操作,放到 DB 從庫上執(zhí)行,有利于減輕 DB 主庫的壓力。
另一方面,將一些耗時離線業(yè)務(wù) sql 放到 DB 從庫上執(zhí)行,能夠減少慢 sql 對 DB 主庫的影響,保證線上業(yè)務(wù)的穩(wěn)定可靠。
解耦性
在軟件工程中,對象之間的耦合度就是對象之間的依賴性。對象之間的耦合越高,維護(hù)成本越高,因此對象的設(shè)計應(yīng)使模塊之間的耦合度盡量小。
在軟件架構(gòu)設(shè)計中,模塊之間的解耦或者說松耦合有兩種,假設(shè)有兩個模塊A、B,A依賴B:
第一種是,模塊A和模塊B只通過接口交互,只要接口設(shè)計不變,那么模塊B內(nèi)部細(xì)節(jié)的變化不影響模塊A對模塊B服務(wù)能力的消費。
1)面向接口設(shè)計下真正實現(xiàn)了將接口契約的定義和接口的實現(xiàn)徹底分離,實現(xiàn)變化不影響到接口契約,自然不影響到基于接口的交互。
2)模塊A和B之間的松耦合,主要通過合理的模塊劃分、接口設(shè)計來完成。如果出現(xiàn)循環(huán)依賴,可以將模塊A、B共同依賴的部分移除到另一個模塊C中,將A、B之間的相互依賴,轉(zhuǎn)換為A、B同時對C的依賴。
第二種是,將同步調(diào)用轉(zhuǎn)換成異步消息交互。
1)比如在買機(jī)票系統(tǒng)中,機(jī)票支付完成后需要通知出票系統(tǒng)出票、代金券系統(tǒng)發(fā)券。如果使用同步調(diào)用,那么出票系統(tǒng)、代金券系統(tǒng)宕機(jī)是會影響到機(jī)票支付系統(tǒng),如果另一個系統(tǒng)比如專車系統(tǒng)也想要在機(jī)票支付完成后向用戶推薦專車服務(wù),那么同步調(diào)用模式下機(jī)票支付系統(tǒng)就需要為此而改動,容易影響核心支付業(yè)務(wù)的可靠性。
2)如果我們將同步調(diào)用替換成異步消息,機(jī)票支付系統(tǒng)發(fā)送機(jī)票支付成功的消息到消息中間件,出票系統(tǒng)、代金券系統(tǒng)從消息中間件訂閱消息。這樣一來,出票系統(tǒng)、代金券系統(tǒng)的宕機(jī)也就不會對機(jī)票支付系統(tǒng)造成任何影響了。專車系統(tǒng)想要知道機(jī)票支付完成這一事件,也只需要從消息中間件訂閱消息即可,機(jī)票支付系統(tǒng)完全不需要做任何改動。
3)異步消息解耦,適合那些信息流單向流動(類似發(fā)布-訂閱這樣的),實時性要求不高的系統(tǒng)。常見的開源消息隊列框架有:Kafka、RabbitMQ、RocketMQ。
限流
為什么要做限流呢?舉一個生活中的例子,大家早上上班都要擠地鐵吧,地鐵站在早高峰的時候經(jīng)常要限制客流,為什么呢?有人會覺得這是人為添堵。真是這樣嗎?如果不執(zhí)行客流控制,大家想想會是什么場景呢?站臺到處都擠滿了乘客,就算你使出洪荒之力也不一定能順利上車,且非常容易引發(fā)肢體碰撞,造成沖突。有了客流控制之后,地鐵站才能變得秩序井然,大家才能安全上地鐵。
一個系統(tǒng)的處理能力是有上限的,當(dāng)服務(wù)請求量超過處理能力,通常會引起排隊,造成響應(yīng)時間迅速提升。
如果對服務(wù)占用的資源量沒有約束,還可能因為系統(tǒng)資源占用過多而宕機(jī)。
因此,為了保證系統(tǒng)在遭遇突發(fā)流量時,能夠正常運行,需要為你的服務(wù)加上限流。
常見的限流算法有:漏桶、令牌桶、滑動窗口計數(shù)。
分類
按照計數(shù)范圍,可以分為:單機(jī)限流、全局限流。單機(jī)限流,一般是為了應(yīng)對突發(fā)流量,而全局限流,通常是為了給有限資源進(jìn)行流量配額。
按照計數(shù)周期,可以分為:QPS、并發(fā)(連接數(shù))。
按照閾值設(shè)定方式的不同,可以分為:固定閾值、動態(tài)閾值。
漏桶算法
下面這張圖,是漏桶的示意圖。漏桶算法思路很簡單,水(請求)先進(jìn)入到漏桶里,漏桶以一定的速度出水,當(dāng)水流入速度過大時,會直接溢出,可以看出漏桶算法能強(qiáng)行限制數(shù)據(jù)的傳輸速率。漏桶算法(Leaky Bucket)是網(wǎng)絡(luò)世界中流量整形(Traffic Shaping)或速率限制(Rate Limiting)時經(jīng)常使用的一種算法,它的主要目的是控制數(shù)據(jù)注入到網(wǎng)絡(luò)的速率,平滑網(wǎng)絡(luò)上的突發(fā)流量。
漏桶算法可以使用 Redis 隊列來實現(xiàn),生產(chǎn)者發(fā)送消息前先檢查隊列長度是否超過閾值,超過閾值則丟棄消息,否則發(fā)送消息到 Redis 隊列中;
消費者以固定速率從 Redis 隊列中取消息。Redis 隊列在這里起到了一個緩沖池的作用,起到削峰填谷、流量整形的作用。
令牌桶算法
對于很多應(yīng)用場景來說,除了要求能夠限制數(shù)據(jù)的平均傳輸速率外,還要求允許某種程度的突發(fā)傳輸。
這時候漏桶算法可能就不合適了,令牌桶算法更為適合。
令牌桶算法的原理是系統(tǒng)會以一個恒定的速度往桶里放入令牌,而如果請求需要被處理,則需要先從桶里獲取一個令牌,當(dāng)桶里沒有令牌可取時,則拒絕服務(wù)。
桶里能夠存放令牌的最高數(shù)量,就是允許的突發(fā)傳輸量。
Guava 中的限流工具 RateLimiter,其原理就是令牌桶算法。
滑動窗口計數(shù)法
計數(shù)法是限流算法里最容易理解的一種,該方法統(tǒng)計最近一段時間的請求量,如果超過一定的閾值,就開始限流。
在 TCP 網(wǎng)絡(luò)協(xié)議中,也用到了滑動窗口來限制數(shù)據(jù)傳輸速率。
滑動窗口計數(shù)有兩個關(guān)鍵的因素:窗口時長、滾動時間間隔。滾動時間間隔一般等于上圖中的一個桶 bucket,窗口時長除以滾動時間間隔,就是一個窗口所包含的 bucket 數(shù)目。
動態(tài)限流
一般情況下的限流,都需要我們手動設(shè)定限流閾值,不僅繁瑣,而且容易因系統(tǒng)的發(fā)布升級而過時。為此,我們考慮根據(jù)系統(tǒng)負(fù)載來動態(tài)決定是否限流,動態(tài)計算限流閾值。可以參考的系統(tǒng)負(fù)載參數(shù)有:Load、CPU、接口響應(yīng)時間等。
降級
業(yè)務(wù)降級,是指犧牲非核心的業(yè)務(wù)功能,保證核心功能的穩(wěn)定運行。簡單來說,要實現(xiàn)優(yōu)雅的業(yè)務(wù)降級,需要將功能實現(xiàn)拆分到相對獨立的不同代碼單元,分優(yōu)先級進(jìn)行隔離。在后臺通過開關(guān)控制,降級部分非主流程的業(yè)務(wù)功能,減輕系統(tǒng)依賴和性能損耗,從而提升集群的整體吞吐率。
降級的重點是:業(yè)務(wù)之間有優(yōu)先級之分。降級的典型應(yīng)用是:電商活動期間關(guān)閉非核心服務(wù),保證核心買買買業(yè)務(wù)的正常運行。
業(yè)務(wù)降級通常需要通過開關(guān)工作,開關(guān)一般做成配置放在專門的配置系統(tǒng),配置的修改最好能夠?qū)崟r生效,畢竟要是還得修改代碼發(fā)布那就太 low 了。開源的配置系統(tǒng)有阿里的diamond、攜程的Apollo、百度的disconf。
降級往往需要兜底方案的配合,比如系統(tǒng)不可用的時候,對用戶進(jìn)行提示,安撫用戶。提示雖然不起眼,但是能夠有效的提升用戶體驗。
熔斷
談到熔斷,不得不提經(jīng)典的電力系統(tǒng)中的保險絲,當(dāng)負(fù)載過大,或者電路發(fā)生故障時,電流會不斷升高,為防止升高的電流有可能損壞電路中的某些重要器件或貴重器件,燒毀電路甚至造成火災(zāi)。保險絲會在電流異常升高到一定的高度和熱度的時候,自身熔斷切斷電流,從而起到保護(hù)電路安全運行的作用。
同樣,在分布式系統(tǒng)中,如果調(diào)用的遠(yuǎn)程服務(wù)或者資源由于某種原因無法使用時,沒有這種過載保護(hù),就會導(dǎo)致請求阻塞在服務(wù)器上等待從而耗盡服務(wù)器資源。很多時候剛開始可能只是系統(tǒng)出現(xiàn)了局部的、小規(guī)模的故障,然而由于種種原因,故障影響的范圍越來越大,最終導(dǎo)致了全局性的后果。而這種過載保護(hù)就是大家俗稱的熔斷器(Circuit Breaker)。
下面這張圖,就是熔斷器的基本原理,包含三個狀態(tài):
1)服務(wù)正常運行時的 Closed 狀態(tài),當(dāng)服務(wù)調(diào)用失敗量或失敗率達(dá)到閾值時,熔斷器進(jìn)入 Open 狀態(tài)
2)在 Open 狀態(tài),服務(wù)調(diào)用不會真正去請求外部資源,會快速失敗。
3)當(dāng)進(jìn)入 Open 狀態(tài)一段時間后,進(jìn)入 Half-Open狀態(tài),需要去嘗試調(diào)用幾次服務(wù),檢查故障的服務(wù)是否恢復(fù)。如果成功則熔斷器關(guān)閉,如果失敗,則再次進(jìn)入 Open 狀態(tài)。
目前比較流行的降級熔斷框架,是由 Netflix 開源的 Hystrix 框架
發(fā)布相關(guān)
模塊級自動化測試
眾所周知,一個項目上線前需要經(jīng)歷嚴(yán)格的測試過程,但是隨著業(yè)務(wù)不斷迭代、系統(tǒng)日益復(fù)雜,研發(fā)工程師、產(chǎn)品經(jīng)理、測試工程師等都在測試過程中投入了大量精力,而一個個線上故障卻表明測試效果并不是那么完美。究其原因,目前的測試工作主要存在兩方面問題:
1)測試范圍難以界定
隨著業(yè)務(wù)邏輯的不斷迭代、系統(tǒng)的不斷拆分與細(xì)化,精確評估項目改動的影響范圍變得越來越困難,從而很難梳理出覆蓋全面的測試點。
2)case驗證成本過高
驗證一個case需要構(gòu)造測試場景,包括數(shù)據(jù)的準(zhǔn)備和運行環(huán)境的準(zhǔn)備,當(dāng)case量較大或者存在一些涉及多個系統(tǒng)模塊且觸發(fā)條件復(fù)雜的case時,這一過程也將花費大量的時間。
解決上述問題可以使用模塊級自動化測試。具體方案是:針對某一模塊,收集模塊線上的輸入、輸出、運行時環(huán)境等信息,在離線測試環(huán)境通過數(shù)據(jù)mock模塊線上場景,回放收集的線上輸入,相同的輸入比較測試場景與線上收集的輸出作為測試結(jié)果。
模塊級自動化測試通過簡化復(fù)雜系統(tǒng)中的不變因素(mock),將系統(tǒng)的測試邊界收攏到改動模塊,將復(fù)雜系統(tǒng)的整體測試轉(zhuǎn)化為改動模塊的單元測試。主要適用于系統(tǒng)業(yè)務(wù)回歸,對系統(tǒng)內(nèi)部重構(gòu)場景尤其適用。
具體如何收集線上數(shù)據(jù)呢?有兩種方法:
1)AOP:面向切面編程,動態(tài)地織入代碼,對原有代碼的侵入性較小。
2)埋點:很多公司都開發(fā)了一下基礎(chǔ)組件,可以在這些基礎(chǔ)組件中嵌入數(shù)據(jù)收集的代碼。
更多細(xì)節(jié),可以查看下面參考文獻(xiàn)中的文章:Qunar 自動化測試框架 ARES。
灰度發(fā)布 & 回滾
單點和發(fā)布是系統(tǒng)高可用最大的敵人。一般在線上出現(xiàn)故障后,第一個要考慮的就是剛剛有沒有代碼發(fā)布、配置發(fā)布,如果有的話就先回滾。線上故障最重要的是快速恢復(fù),如果等你細(xì)細(xì)看代碼找到問題,沒準(zhǔn)兒半天就過去了。
為了減少發(fā)布引起問題的嚴(yán)重程度,通常會使用灰度發(fā)布策略。灰度發(fā)布是速度與安全性作為妥協(xié)。他是發(fā)布眾多保險的最后一道,而不是唯一的一道。
做灰度發(fā)布,如果是勻速的,說明沒有理解灰度發(fā)布的意義。一般來說階段選擇上從 1% -> 10% -> 100% 的指數(shù)型增長。這個階段,是根據(jù)具體業(yè)務(wù)不同按維度去細(xì)分的。
這里面的重點在于 1% 并不全是隨機(jī)選擇的,而是根據(jù)業(yè)務(wù)特點、數(shù)據(jù)特點選擇的一批有極強(qiáng)的代表性的實例,去做灰度發(fā)布的小白鼠。甚至于每次發(fā)布的 第一階段用戶(我們叫 Canary/金絲雀),根據(jù)每次發(fā)布的特點不同,是人為挑選的。
發(fā)布之前必須制定詳細(xì)的回滾步驟,回滾是解決發(fā)布引起的故障的最快的方法。
故障演練
為什么要做故障演練呢?就跟在測試業(yè)務(wù)功能時,不僅要測試正常的請求能否正確處理,也要測試異常的請求能否得到適當(dāng)?shù)奶幚硪粯印U驹谌值慕嵌瓤?#xff0c;我們也希望保證某個機(jī)器或某個服務(wù)掛掉時,盡量不影響系統(tǒng)整體的可用性,技術(shù)上要靠無狀態(tài)服務(wù)、冗余部署、降級等。實際中如何測試這樣的異常情況呢?
Netflix 開源了一個工具 Chaos Monkey,這是一套用來故意把服務(wù)器搞下線的軟件,可以用來測試系統(tǒng)的健壯性和恢復(fù)能力。
自動化運維-故障自愈
在文章阿里如何做到百萬量級硬件故障自愈里,介紹了如何實現(xiàn)硬件故障預(yù)測、服務(wù)器自動下線、服務(wù)自愈以及集群的自平衡重建,真正在影響業(yè)務(wù)之前實現(xiàn)硬件故障自動閉環(huán)策略,對于常見的硬件故障無需人工干預(yù)即可自動閉環(huán)解決。
事件系統(tǒng)
AWS 有一個 CloudTrail 系統(tǒng),專門記錄重大活動事件,可以簡化安全性分析、資源更改跟蹤和問題排查工作。系統(tǒng)發(fā)布、配置變更是引發(fā)故障的一大因素,微服務(wù)化的系統(tǒng)架構(gòu)里,有時某個底層系統(tǒng)的變更,引起反映、出現(xiàn)故障的往往是上層直接面對用戶的系統(tǒng)。有了事件系統(tǒng),出現(xiàn)故障后,可以快速查看在故障時間點,相關(guān)聯(lián)系統(tǒng)是否有變更,是否是引起故障的根本原因?
事件系統(tǒng)的出現(xiàn),可以幫助應(yīng)用開發(fā)者快速定位“底層系統(tǒng)變更引發(fā)上層系統(tǒng)異常”這一類故障的根本原因。
其他設(shè)計考慮
1)設(shè)置超時
請求對外接口的時候,需要設(shè)置合理的超時時間,避免外部接口掛掉時,阻塞整個系統(tǒng)。
2)失敗重試
失敗重試能夠提高成功率,但是也會造成響應(yīng)時間變慢,服務(wù)提供方壓力倍增。具體要不要重試要根據(jù)具體情況決定:對響應(yīng)時間有要求嗎?接口失敗率如何?重試會不會造成雪崩?
總結(jié)
出處:http://www.uml.org.cn/zjjs/202007031.asp?artid=23459
總結(jié)
以上是生活随笔為你收集整理的高可用系统架构设计 技术方案的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Akka框架
- 下一篇: Kudu - 一个融合低延迟写入和高性能