系統設計:關于高可用系統的一些技術方案

[TOC]

系統設計:關于高可用系統的一些技術方案

可靠的系統是業務穩定、快速發展的基石。那么,如何做到系統高可靠、高可用呢?下面從技術方面介紹幾種提高系統可靠性、可用性的方法。

擴展

擴展是最常見的提升系統可靠性的方法,系統的擴展可以避免單點故障,即一個節點出現了問題造成整個系統無法正常工作。換一個角度講,一個容易擴展的系統,能夠通過擴展來成倍的提升系統能力,輕松應對系統訪問量的提升。

一般地,擴展可以分為垂直擴展和水平擴展:

  1. 垂直擴展:是在同一邏輯單元里添加資源從而滿足系統處理能力上升的需求。比如,當機器內存不夠時,我們可以幫機器增加內存,或者數據存不下時,我們為機器掛載新的磁盤。
    • 垂直擴展能夠提升系統處理能力,但不能解決單點故障問題
    • 優點:擴展簡單。
    • 缺點:擴展能力有限。
  2. 水平擴展:通過增加一個或多個邏輯單元,并使得它們像整體一樣的工作。
    • 水平擴展,通過冗余部署解決了單點故障,同時又提升了系統處理能力。
    • 優點:擴展能力強。
    • 缺點:增加系統復雜度,維護成本高,系統需要是無狀態的、可分布式的。

可擴展性系數 scalability factor 通常用來衡量一個系統的擴展能力,當增加 1 單元的資源時,系統處理能力只增加了 0.95 單元,那么可擴展性系數就是 95%。當系統在持續的擴展中,可擴展系數始終保持不變,我們就稱這種擴展是線性可擴展。

在實際應用中,水平擴展最常見:

  1. 通常我們在部署應用服務器的時候,都會部署多臺,然后使用 nginx 來做負載均衡,nginx 使用心跳機制來檢測服務器的正常與否,無響應的服務就從集群中剔除。這樣的集群中每臺服務器的角色是相同的,同時提供一樣的服務。
  2. 在數據庫的部署中,為了防止單點故障,一般會使用一主多從,通常寫操作只發生在主庫。不同數據庫之間角色不同。當主機宕機時,一臺從庫可以自動切換為主機提供服務。

隔離

隔離,是對什么進行隔離呢?是對系統、業務所占有的資源進行隔離,限制某個業務對資源的占用數量,避免一個業務占用整個系統資源,對其他業務造成影響。

隔離級別按粒度從小到大,可以分為線程池隔離、進程隔離、模塊隔離、應用隔離、機房隔離。在數據庫的使用中,還經常用到讀寫分離。

  1. 線程池隔離:不同的業務使用不同的線程池,避免低優先級的任務阻塞高優先級的任務。或者高優先級的任務過多,導致低優先級任務永遠不會執行。
  2. 進程隔離:Linux 中有用于進程資源隔離的 Linux CGroup,通過物理限制的方式為進程間資源控制提供了簡單的實現方式,為 Linux Container 技術、虛擬化技術的發展奠定了技術基礎。在工作中的實際應用,可以看看這篇文章:日志壓縮資源消耗優化: Linux CGroup 的使用
  3. 模塊隔離、應用隔離:很多線上故障的發生源于代碼修改后,測試不到位導致。按照代碼或業務的易變程度來劃分模塊或應用,把變化較少的劃分到一個模塊或應用中,變化較多的劃分到另一個模塊或應用中。減少代碼修改影響的范圍,也就減少了測試的工作量,減少了故障出現的概率。
  4. 機房隔離:主要是為了避免單個機房網絡問題或斷電吧。
  5. 讀寫分離:一方面,將對實時性要求不高的讀操作,放到 DB 從庫上執行,有利于減輕 DB 主庫的壓力。另一方面,將一些耗時離線業務 sql 放到 DB 從庫上執行,能夠減少慢 sql 對 DB 主庫的影響,保證線上業務的穩定可靠。

解耦

在軟件工程中,對象之間的耦合度就是對象之間的依賴性。對象之間的耦合越高,維護成本越高,因此對象的設計應使模塊之間的耦合度盡量小。在軟件架構設計中,模塊之間的解耦或者說松耦合有兩種,假設有兩個模塊A、B,A依賴B:

  1. 第一種是,模塊A和模塊B只通過接口交互,只要接口設計不變,那么模塊B內部細節的變化不影響模塊A對模塊B服務能力的消費。
    • 面向接口設計下真正實現了將接口契約的定義和接口的實現徹底分離,實現變化不影響到接口契約,自然不影響到基于接口的交互。
    • 模塊A和B之間的松耦合,主要通過合理的模塊劃分、接口設計來完成。如果出現循環依賴,可以將模塊A、B共同依賴的部分移除到另一個模塊C中,將A、B之間的相互依賴,轉換為A、B同時對C的依賴。
  2. 第二種是,將同步調用轉換成異步消息交互。
    • 比如在買機票系統中,機票支付完成后需要通知出票系統出票、代金券系統發券。如果使用同步調用,那么出票系統、代金券系統宕機是會影響到機票支付系統,如果另一個系統比如專車系統也想要在機票支付完成后向用戶推薦專車服務,那么同步調用模式下機票支付系統就需要為此而改動,容易影響核心支付業務的可靠性。
    • 如果我們將同步調用替換成異步消息,機票支付系統發送機票支付成功的消息到消息中間件,出票系統、代金券系統從消息中間件訂閱消息。這樣一來,出票系統、代金券系統的宕機也就不會對機票支付系統造成任何影響了。專車系統想要知道機票支付完成這一事件,也只需要從消息中間件訂閱消息即可,機票支付系統完全不需要做任何改動。
    • 異步消息解耦,適合那些信息流單向流動(類似發布-訂閱這樣的),實時性要求不高的系統。常見的開源消息隊列框架有:Kafka、RabbitMQ、RocketMQ。

限流

為什么要做限流呢?舉一個生活中的例子,大家早上上班都要擠地鐵吧,地鐵站在早高峰的時候經常要限制客流,為什么呢?有人會覺得這是人為添堵。真是這樣嗎?如果不執行客流控制,大家想想會是什么場景呢?站臺到處都擠滿了乘客,就算你使出洪荒之力也不一定能順利上車,且非常容易引發肢體碰撞,造成沖突。有了客流控制之后,地鐵站才能變得秩序井然,大家才能安全上地鐵。

一個系統的處理能力是有上限的,當服務請求量超過處理能力,通常會引起排隊,造成響應時間迅速提升。如果對服務占用的資源量沒有約束,還可能因為系統資源占用過多而宕機。因此,為了保證系統在遭遇突發流量時,能夠正常運行,需要為你的服務加上限流。

常見的限流算法有:漏桶、令牌桶、滑動窗口計數。

分類

按照計數范圍,可以分為:單機限流、全局限流。單機限流,一般是為了應對突發流量,而全局限流,通常是為了給有限資源進行流量配額。

按照計數周期,可以分為:QPS、并發(連接數)。

按照閾值設定方式的不同,可以分為:固定閾值、動態閾值。

漏桶算法

下面這張圖,是漏桶的示意圖。漏桶算法思路很簡單,水(請求)先進入到漏桶里,漏桶以一定的速度出水,當水流入速度過大時,會直接溢出,可以看出漏桶算法能強行限制數據的傳輸速率。漏桶算法(Leaky Bucket)是網絡世界中流量整形(Traffic Shaping)或速率限制(Rate Limiting)時經常使用的一種算法,它的主要目的是控制數據注入到網絡的速率,平滑網絡上的突發流量。

漏桶算法原理

漏桶算法可以使用 Redis 隊列來實現,生產者發送消息前先檢查隊列長度是否超過閾值,超過閾值則丟棄消息,否則發送消息到 Redis 隊列中;消費者以固定速率從 Redis 隊列中取消息。Redis 隊列在這里起到了一個緩沖池的作用,起到削峰填谷、流量整形的作用。

令牌桶算法

對于很多應用場景來說,除了要求能夠限制數據的平均傳輸速率外,還要求允許某種程度的突發傳輸。這時候漏桶算法可能就不合適了,令牌桶算法更為適合。令牌桶算法的原理是系統會以一個恒定的速度往桶里放入令牌,而如果請求需要被處理,則需要先從桶里獲取一個令牌,當桶里沒有令牌可取時,則拒絕服務。桶里能夠存放令牌的最高數量,就是允許的突發傳輸量。

令牌桶算法原理

Guava 中的限流工具 RateLimiter,其原理就是令牌桶算法。

滑動窗口計數法

計數法是限流算法里最容易理解的一種,該方法統計最近一段時間的請求量,如果超過一定的閾值,就開始限流。在 TCP 網絡協議中,也用到了滑動窗口來限制數據傳輸速率。

滑動窗口計數原理

滑動窗口計數有兩個關鍵的因素:窗口時長、滾動時間間隔。滾動時間間隔一般等于上圖中的一個桶 bucket,窗口時長除以滾動時間間隔,就是一個窗口所包含的 bucket 數目。

滑動窗口計數算法的實現,可以查看這篇文章:降級熔斷框架 Hystrix 源碼解析:滑動窗口統計

動態限流

一般情況下的限流,都需要我們手動設定限流閾值,不僅繁瑣,而且容易因系統的發布升級而過時。為此,我們考慮根據系統負載來動態決定是否限流,動態計算限流閾值。可以參考的系統負載參數有:Load、CPU、接口響應時間等。

動態限流

降級

業務降級,是指犧牲非核心的業務功能,保證核心功能的穩定運行。簡單來說,要實現優雅的業務降級,需要將功能實現拆分到相對獨立的不同代碼單元,分優先級進行隔離。在后臺通過開關控制,降級部分非主流程的業務功能,減輕系統依賴和性能損耗,從而提升集群的整體吞吐率。

降級的重點是:業務之間有優先級之分。降級的典型應用是:電商活動期間關閉非核心服務,保證核心買買買業務的正常運行。

業務降級通常需要通過開關工作,開關一般做成配置放在專門的配置系統,配置的修改最好能夠實時生效,畢竟要是還得修改代碼發布那就太 low 了。開源的配置系統有阿里的diamond、攜程的Apollo、百度的disconf

降級往往需要兜底方案的配合,比如系統不可用的時候,對用戶進行提示,安撫用戶。提示雖然不起眼,但是能夠有效的提升用戶體驗。

熔斷

談到熔斷,不得不提經典的電力系統中的保險絲,當負載過大,或者電路發生故障時,電流會不斷升高,為防止升高的電流有可能損壞電路中的某些重要器件或貴重器件,燒毀電路甚至造成火災。保險絲會在電流異常升高到一定的高度和熱度的時候,自身熔斷切斷電流,從而起到保護電路安全運行的作用。

同樣,在分布式系統中,如果調用的遠程服務或者資源由于某種原因無法使用時,沒有這種過載保護,就會導致請求阻塞在服務器上等待從而耗盡服務器資源。很多時候剛開始可能只是系統出現了局部的、小規模的故障,然而由于種種原因,故障影響的范圍越來越大,最終導致了全局性的后果。而這種過載保護就是大家俗稱的熔斷器(Circuit Breaker)。

下面這張圖,就是熔斷器的基本原理,包含三個狀態:

  1. 服務正常運行時的 Closed 狀態,當服務調用失敗量或失敗率達到閾值時,熔斷器進入 Open 狀態
  2. 在 Open 狀態,服務調用不會真正去請求外部資源,會快速失敗。
  3. 當進入 Open 狀態一段時間后,進入 Half-Open狀態,需要去嘗試調用幾次服務,檢查故障的服務是否恢復。如果成功則熔斷器關閉,如果失敗,則再次進入 Open 狀態。
熔斷器基本原理

目前比較流行的降級熔斷框架,是由 Netflix 開源的 Hystrix 框架

發布相關

模塊級自動化測試

眾所周知,一個項目上線前需要經歷嚴格的測試過程,但是隨著業務不斷迭代、系統日益復雜,研發工程師、產品經理、測試工程師等都在測試過程中投入了大量精力,而一個個線上故障卻表明測試效果并不是那么完美。究其原因,目前的測試工作主要存在兩方面問題:

  1. 測試范圍難以界定:隨著業務邏輯的不斷迭代、系統的不斷拆分與細化,精確評估項目改動的影響范圍變得越來越困難,從而很難梳理出覆蓋全面的測試點。
  2. case驗證成本過高:驗證一個case需要構造測試場景,包括數據的準備和運行環境的準備,當case量較大或者存在一些涉及多個系統模塊且觸發條件復雜的case時,這一過程也將花費大量的時間。

解決上述問題可以使用模塊級自動化測試。具體方案是:針對某一模塊,收集模塊線上的輸入、輸出、運行時環境等信息,在離線測試環境通過數據mock模塊線上場景,回放收集的線上輸入,相同的輸入比較測試場景與線上收集的輸出作為測試結果。

模塊級自動化測試通過簡化復雜系統中的不變因素(mock),將系統的測試邊界收攏到改動模塊,將復雜系統的整體測試轉化為改動模塊的單元測試。主要適用于系統業務回歸,對系統內部重構場景尤其適用。

具體如何收集線上數據呢?有兩種方法:

  1. AOP:面向切面編程,動態地織入代碼,對原有代碼的侵入性較小。
  2. 埋點:很多公司都開發了一下基礎組件,可以在這些基礎組件中嵌入數據收集的代碼。

更多細節,可以查看下面參考文獻中的文章:Qunar 自動化測試框架 ARES。

灰度發布 & 回滾

單點和發布是系統高可用最大的敵人。一般在線上出現故障后,第一個要考慮的就是剛剛有沒有代碼發布、配置發布,如果有的話就先回滾。線上故障最重要的是快速恢復,如果等你細細看代碼找到問題,沒準兒半天就過去了。

為了減少發布引起問題的嚴重程度,通常會使用灰度發布策略。灰度發布是速度與安全性作為妥協。他是發布眾多保險的最后一道,而不是唯一的一道。在這篇文章來自 Google 的高可用架構理念與實踐里提到:

做灰度發布,如果是勻速的,說明沒有理解灰度發布的意義。一般來說階段選擇上從 1% -> 10% -> 100% 的指數型增長。這個階段,是根據具體業務不同按維度去細分的。
這里面的重點在于 1% 并不全是隨機選擇的,而是根據業務特點、數據特點選擇的一批有極強的代表性的實例,去做灰度發布的小白鼠。甚至于每次發布的 第一階段用戶(我們叫 Canary/金絲雀),根據每次發布的特點不同,是人為挑選的。

發布之前必須制定詳細的回滾步驟,回滾是解決發布引起的故障的最快的方法。

其他

  1. 設置超時:請求對外接口的時候,需要設置合理的超時時間,避免外部接口掛掉時,阻塞整個系統。
  2. 失敗重試:失敗重試能夠提高成功率,但是也會造成響應時間變慢,服務提供方壓力倍增。具體要不要重試要根據具體情況決定:對響應時間有要求嗎?接口失敗率如何?重試會不會造成雪崩?

總結

技術 解決什么問題
擴展 通過冗余部署,避免單點故障
隔離 1. 避免業務之間的相互影響 2. 機房隔離避免單點故障
解耦 減少依賴,減少相互間的影響
限流 遇到突發流量時,保證系統穩定
降級 犧牲非核心業務,保證核心業務的高可用
熔斷 減少不穩定的外部依賴對核心服務的影響
自動化測試 通過完善的測試,減少發布引起的故障
灰度發布 灰度發布是速度與安全性作為妥協,能夠有效減少發布故障

在這篇文章中,我們探討了一些提供系統可靠性的技術方案。關于高可用的更多問題可以看看這篇文章 陳皓:關于高可用的系統,這篇文章的核心在于提出:

5個9的SLA在一年內只能是5分鐘的不可用時間,5分鐘啊,如果按一年只出1次故障,你也得在五分鐘內恢復故障,讓我們想想,這意味著什么?
如果你沒有一套科學的牛逼的軟件工程的管理,沒有牛逼先進的自動化的運維工具,沒有技術能力很牛逼的工程師團隊,怎么可能出現高可用的系統啊。
是的,要干出高可用的系統,這TMD就是一套嚴謹科學的工程管理。

參考資料

  1. 分布式常見術語(單點故障、可擴展性、元數據服務器)
  2. 日志壓縮資源消耗優化: Linux CGroup 的使用
  3. 解耦
  4. 如何建設高可用系統
  5. 史上最復雜業務場景,逼出阿里高可用三大法寶
  6. 萬億級數據洪峰下的分布式消息引擎
  7. 談談我對服務熔斷、服務降級的理解
  8. 接口限流算法總結
  9. 降級熔斷框架 Hystrix 源碼解析:滑動窗口統計
  10. Netflix Hystrix 框架官方文檔
  11. CGroup 介紹、應用實例及原理描述
  12. 關于高可用的系統
  13. Qunar自動化測試框架ARES
  14. 來自 Google 的高可用架構理念與實踐
  15. 高可用可伸縮架構實用經驗談
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,881評論 18 139
  • gem 'table_for_collection' table_for_collection is a simp...
    KK的記錄閱讀 140評論 0 1
  • 十年前的此時此刻,我躺在手術室里,醫生一邊忙活著一邊問我:“你想要個啥?”我忍著,斷斷續續的說:“妹妹……”。只覺...
    潤語linlin閱讀 151評論 0 2
  • 我記得第一次認識你是在小芮芮的公眾號上,或許是出于好奇,或許是出于無聊,我打開了網易云音樂,在搜索欄里輸入了“崔...
    Amanda_77閱讀 372評論 0 2
  • 連續幾天下雨,幾天沒有跑步。好不容易今晚雨停了,趕緊出去跑跑步。 路面濕滑,不敢跑太快,也沒有跑的太久,遛遛彎往回...
    雋兒姐姐閱讀 220評論 0 0