關於系統高可用的思考

weixin_34006468發表於2017-11-07

大型分散式系統在實際的執行過程中面對的情況是非常複雜的,業務上的流量突增、依賴服務的不穩定、應用自身的瓶頸、物理資源的損壞等方方面面都會對系統的執行帶來大大小小的的衝擊。如何保障應用在複雜的工況環境下還能高效穩定執行,如何預防和麵對突發問題,在設計和保障系統的時候應該從哪些方面著手?

本文將從單機效能優化、叢集容量控制、分散式下高可用設計、系統容災方面,探討系統的高可用設計和保障。

1、單機效能優化


應用程式的效能度量標準一般分為吞吐量和延遲。吞吐量是指程式能夠達到的最高效能指標。延遲是指請求到響應之間的整體延遲時間。分散式系統中,任一環節出現延遲變高時,都會導致該節點不可用,甚至不可用的狀態會擴散至其它節點,引起整個分散式系統的“雪崩”,最終導致服務不可用。所以打造低延遲的應用程式,提升應用的單機效能對提升整個分散式系統可用性有很大的幫助。

對於效能優化可以參考關於伺服器效能的思考關於伺服器效能優化的思考和實踐這兩篇文章。

2、叢集容量控制


有了單機效能的優化保障,系統是否已經是高枕無憂?不一定,效能優化只是提升了單機的處理能力,保障系統不會被某一次的請求而卡住。但面對洶湧而來的流量洪峰,即便叢集部署了多臺機器,整個系統也無法保障一定會穩定執行,某一次突然遠高於預期的洪峰或者依賴的後端服務的一次持續抖動,就會導致整個叢集的執行的不穩定。極端情況下,在高位持續執行機器可能會因為一次壓力的加大,超過承受能力,帶來整個服務的停頓,應用的Crash,進而可能將延遲傳遞給服務呼叫方造成整個系統的服務能力喪失而產生雪崩。

因此即使前期各種壓測、容量規劃、預案做的如何充分,面對不可預知的突發情況,依然無法做到真正的如絲般順滑。為了防止自己的應用被打死或者被拖死,在系統設計的時候需要考慮到限流、降級和熔斷,丟卒保車,以降級、暫停邊緣服務、元件件為代價保障核心服務的資源,以系統不被突發流量擊垮為第一要務。

2.1 限流

顧名思義就是對請求應用的流量進行限制,對於超過限流閾值的流量進行丟棄,用於保護系統處於一個合理的流量壓力之下,不會因為突發的不可預知的大量請求打死。

典型的限流演算法有漏桶(leaky bucket)演算法和令牌桶(token bucket)演算法。


6751690-bde9713dffa73ed4.png
漏桶演算法

漏桶演算法基本思路是有一個桶(會漏水),水以恆定速率滴出,上方會有水滴(請求)進入水桶。如果上方水滴進入速率超過水滴出的速率,那麼水桶就會溢位,即請求過載。

6751690-56d36f0c216b424c.png
令牌桶演算法

令牌桶演算法基本思路是同樣也有一個桶,令牌以恆定速率放入桶,桶內的令牌數有上限,每個請求會acquire一個令牌,如果某個請求來到而桶內沒有令牌了,則這個請求是過載的。

漏桶演算法和令牌桶演算法對比

令牌桶演算法中會以恆定的速率如每秒n個放入桶中,如果桶的容量上限設定為1s放入的令牌大小,那麼功能上令牌桶演算法會退化為漏桶演算法,其qps控速為n/s。

如果令牌桶的容量被設定為大於1s放入的令牌大小,如設定兩秒,則其容量為2n,那麼也就是說在流量突增的瞬間的qps控速會達到2n/s,然後恢復到n/s,很明顯令牌桶會存在請求突發激增的問題。但一般系統執行都會在平均load以下,如4核機器,load一般在4以下(如果長時間處於4以上,則需要考慮系統執行是否有異常),偶爾短時間的流量激增是可以被系統慢慢消化的,只是不能長時間的處於流量超量的情況。所以令牌桶演算法相比漏桶演算法有更大的靈活性。

演算法實現

常用的限流演算法的實現有Guava RateLimiter,它提供了漏桶演算法和令牌桶演算法的實現。

2.2 降級

服務降級是一種典型的丟卒保車,二八原則實踐,而降級的手段也無外乎關閉,下線等“簡單粗暴”的操作。降級是對業務有損的,因此什麼能降,什麼不能降,什麼時候能降,什麼時候不能降,都需要提前和業務方確認,做好強弱依賴梳理。

2.3 熔斷

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

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

熔斷可以看著是自動化的降級,因此也可能是對業務有損的,在使用熔斷服務時需要確定好,被熔斷的服務不是應用的核心鏈路。

熔斷服務的核心在於如何判斷是否要熔斷、何時進行熔斷恢復。基於滑動視窗的訊息靜默限流,介紹了Neflix實現的Hystrix,並借鑑其思想在後端定時服務不穩定時,主動停止對定時服務的請求,通過訊息中間蓄洪,等待後端服務恢復穩定後再開始請求。

2.4 容量控制的代價

限流的確是保障自身應用穩定的利器,在應用的維護方看來,保證自己的業務不被打死是第一要務,但是限流卻會給依賴方帶來影響,對他們的業務是有損的。在複雜業務中,往往是許多應用相互依賴,你限別人的同時,別人也會限制你,所以在大促的前期需要多方確認限流的場景和限流的值,有時甚至會反覆討論和討價還價,付出巨大的溝通和時間成本。

特別是一些核心應用,比如交易系統作為整個大促的核心,不僅承擔著大量的寫入請求,實際還承擔著天量讀取請求,大量其他應用強烈依賴對交易系統的反查來獲取交易的詳情資訊和狀態,如物流、訂單、優惠等。但是交易系統的處理能力也不是無限的,所以在大促前的準備工作中,對依賴交易的應用進行梳理,確定限流值、限流場景、限流時長成為一項異常繁重的工作,往往需要一個個應用的排查,一個個業務去溝通討論。

3、分散式高可用系統設計


對於無狀態的服務,如果做好了單機效能優化和容量控制,基本上可以面對大部分突發情況,無非就是流量過大先限流再擴容,後端服務不穩定則降級,即便是出現應用crash、機器損壞等極端情況,也只需要做好流量隔離即可。

但是對於有狀態的應用,則必須考慮極端情況下資料的完整性和服務的可用性,而為了保證資料的完整性通常又會引入資料備份,而引入資料備份則又會導致一致性問題。在分散式的高可用設計中,這類問題被歸結為CAP,即一致性、可用性和分割槽容錯性三者無法在分散式系統中被同時滿足,並且最多隻能滿足其中兩個。

對於分散式系統的高可用方案,業界有一些通用的解決方案:


6751690-81d6d4d94ad3e617.png
分散式高可用方案對比

其中橫軸代表了分散式系統中通用的高可用解決方案,如:冷備、Master/Slave、Master/Master、兩階段提交以及基於Paxos演算法的解決方案;縱軸代表了分散式系統所關心的各項指標,包括資料一致性、事務支援的程度、資料延遲、系統吞吐量、資料丟失可能性、故障自動恢復時間。

對於Paxos演算法感興趣的朋友,可以看看raft演算法在軟負載中的研究,raft演算法假定了Paxos演算法的特定使用場景,可以看成是Paxos的簡化版。

3.1 RocketMQ的高可用設計

這裡對RocketMQ的高可用設計進行介紹和分析。

RocketMQ訊息引擎基於多機房部署結構,依託於Zookeeper的分散式鎖和通知機制,引入Controller元件負責Broker狀態的監控以及主備狀態機轉換,設計了一套Master/Slave結構的高可用架構。

6751690-b54a18531e79be9e.png
RocketMQ高可用設計

Zookeeper Server作為分散式服務框架,需要至少在A、B、C三個機房部署以保證其高可用,為RocketMQ高可用架構提供如下功能:

  • 維護持久節點(PERSISTENT),用來儲存主備狀態機
  • 維護臨時節點(EPHEMERAL),用來儲存RocketMQ Broker的當前狀態
  • 當Broker程式消失,該臨時節點也將不復存在
  • 當主備狀態機、服務端當前狀態發生變更時,通知對應的觀察者

RocketMQ Broker作為直接面向使用者提供高可靠訊息服務的資料節點,首先需要保證以Master/Slave結構實現多機房對等部署,即機房A中的Master對應的Slave會部署在另外一個機房B,並且一個叢集中的Master會均衡地分佈到所有機房中;訊息的寫請求會命中Master,然後通過同步或者非同步方式複製到Slave上進行持久化儲存;訊息的讀請求會優先命中Master,特殊情況下(訊息堆積讀訊息導致磁碟壓力大)讀請求會轉移至Slave。

RocketMQ Broker直接與Zookeeper Server進行互動。體現在:

  • 以臨時節點的方式向Zookeeper彙報Broker當前狀態;
  • 作為觀察者監聽Zookeeper上以持久節點方式儲存的主備狀態機的變更;

當監聽發現Zookeeper上的主備狀態機發生變化時,根據最新的狀態機更改Broker當前狀態;RocketMQ HA Controller是RocketMQ高可用架構中為了降低系統故障恢復時間而引入的無狀態元件,在A、B、C三個機房分散式部署,其主要職責體現在:

  • 作為觀察者監聽Zookeeper上以臨時節點方式儲存的Broker當前狀態的變更;
  • 根據叢集中所有Broker的當前狀態,控制主備狀態機的切換並以持久節點的方式向Zookeeper彙報最新主備狀態機。
  • 出於對系統複雜性以及軟體本身對CAP原則的適配考慮,RocketMQ高可用架構的設計採用了Master/Slave結構,在提供低延遲、高吞吐量訊息服務的基礎上,採用主備同步複製的方式避免故障時訊息的丟失,同時引入故障自動恢復機制以降低故障恢復時間,提升整個系統的SLA。

3.2 RocketMQ資料可靠性的分析

訊息系統的資料可靠性包括資料不丟失和資料不重複。

RocketMQ作為一款訊息型中介軟體,它提供At least Once的特性,即保證訊息不丟失,但是不提供Exactly Only Once的特性,即不保證訊息不重複。從之前的分析知道,即便訊息在m/s上都儲存成功,但是如果
master在回ack給producer的時候失敗,producer會重新進行投遞,在分散式環境中要保證訊息的不重複需要付出巨大的代價,因此RocketMQ不提供Exactly Only Once的特性。

RocketMQ的這個問題的解決方案是交給訊息中介軟體的使用者,在消費端需要做訊息冪等。

3.3 資料可靠性和效能之間的選擇?

訊息型中介軟體最重要的特性之一就是保證訊息的不丟失,然而訊息不丟失是以犧牲應用效能為代價的。為了保證嚴格的訊息不丟失,一條訊息在產生和消費的過程中由producer、broker、consumer三者來共同保證。

其中最核心的是broker端保證訊息不能丟失,為此在寫master時需要同步將訊息複製到slave,並且等slave上持久化成功並通知master,master在確認master和slave都持久化成功之後才能發ack到producer確認訊息儲存成功。

在嚴格資料可靠性的保障下,主備之間的同步備份和持久化是一個比較耗時的過程,這會使整個訊息系統的吞吐量大大降低。因此RocketMQ提供了非同步備份的選項,在不需要嚴格資料可靠性的業務中,可以選擇訊息的非同步備份,比如:包裹的狀態變更訊息,一個包裹狀態由發貨變更為運輸、派送、簽收,缺失其中一個狀態變更的訊息並不會引起災難性的後果。

3.4 擴充套件 - mysql(innodb)的資料可靠性對效能的影響

mysql(innodb)也是採用了典型的Master/Slave結構來保證資料的可靠性,同時為了實現事務,mysql(innodb)實現了很多日誌,如:binlog、redolog、undolog,其日誌和同步策略也分為非同步和同步,分別由以下引數控制:

  • innodb_flush_log_at_trx_commit

    0:log buffer將每秒一次地寫入log file中,並且log file的flush(刷到磁碟)操作同時進行.該模式下,在事務提交的時候,不會主動觸發寫入磁碟的操作。

    1:每次事務提交時MySQL都會把log buffer的資料寫入log file,並且flush(刷到磁碟)中去.

    2:每次事務提交時MySQL都會把log buffer的資料寫入log file.但是flush(刷到磁碟)操作並不會同時進行。該模式下,MySQL會每秒執行一次 flush(刷到磁碟)操作。

  • sync_binlog

    0:MySQL不控制binlog的重新整理,由檔案系統自己控制它的快取的重新整理。

    1:每次事務提交,MySQL都會把binlog刷到磁碟。

    >0:每次事務提交,MySQL呼叫檔案系統的重新整理操作將快取刷下去。

在正常的執行過程中,這兩個引數一般設定為1,即雙1設定,資料可靠性最高但是效能最低。但在大促和壓測期間可以選擇性的設定為雙0,非同步去刷盤。對比發現,對效能的提升在一倍以上,對db io的使用下降約一倍。

通過允許少量的資料丟失帶來的效能提升還是很客觀的,當然如果對資料可靠性要求極其嚴格,如交易金融級別的,還是需要同步刷盤。

6751690-9d011b7e92423b84.png
db效能監控
6751690-91760f9c0d87a0c6.png
db磁碟io

零點左右的峰值是壓測流量。

4、系統容災


系統容災是在指在高可用設計的架構下,當應用出現問題的時候,如何保障系統繼續正常穩定提供服務。

應用執行過程中可能出現的問題:

  • 應用正常關閉
  • 應用異常 Crash
  • OS Crash
  • 機器掉電,但是能立即恢復供電情況
  • 機器無法開機(可能是cpu、主機板、記憶體等關鍵裝置損壞)
  • 磁碟裝置損壞

在這些情況下,需要考慮如何對外提供穩定的服務,以RocketMQ為例:

6751690-36f39ed3cea59418.jpg
RocketMQ高可用狀態切換
  • 第一個節點啟動後,Controller控制狀態機切換為單主狀態,通知啟動節點以Master角色提供服務。
  • 第二個節點啟動後,Controller控制狀態機切換成非同步複製狀態。Master通過非同步方式向Slave複製資料。
  • 當Slave的資料即將趕上Master,Controller控制狀態機切換成半同步狀態,此時命中Master的寫請求會被Hold住,直到Master以非同步方式向Slave複製了所有差異的資料。
  • 當半同步狀態下Slave的資料完全趕上Master時,Controller控制狀態機切換成同步複製模式,Mater開始以同步方式向Slave複製資料。該狀態下任一節點出現故障,其它節點能夠在秒級內切換到單主狀態繼續提供服務。

Controller元件控制RocketMQ按照單主狀態,非同步複製狀態,半同步狀態,同步複製狀態的順序進行狀態機切換。中間狀態的停留時間與主備之間的資料差異以及網路頻寬有關,但最終都會穩定在同步複製狀態下。

相關文章