萬億級資料洪峰下的分散式訊息引擎

技術瑣話發表於2018-12-17

前言

通過簡單回顧阿里中介軟體(Aliware)訊息引擎的發展史,本文開篇於雙11訊息引擎面臨的低延遲挑戰,通過經典的應用場景闡述可能會面臨的問題 - 響應慢,雪崩,使用者體驗差,繼而交易下跌。為了應對這些不可控的洪峰資料,中介軟體團隊通過大量研究和實踐,推出了低延遲高可用解決方案,在分散式儲存領域具有一定的普適性。在此基礎上,通過對現有有限資源的規劃,又推出了分級的容量保障策略,通過限流、降級,甚至熔斷技術,能夠有效保障重點業務的高吞吐,成功的支撐集團包括海外業務平緩舒暢地度過雙11高峰。與此同時,在一些對高可靠、高可用要求極為苛刻的場景下,中介軟體團隊又重點推出了基於多副本機制的高可用解決方案,能夠動態識別機器當機、機房斷網等災難場景,自動實現主備切換。整個切換過程對使用者透明,運維開發人員無需干預,極大地提升訊息儲存的可靠性以及整個叢集的高可用性。

1. 訊息引擎家族史

阿里中介軟體訊息引擎發展到今日,前前後後經歷了三代演進。第一代,推模式,資料儲存採用關係型資料庫。在這種模式下,訊息具有很低的延遲特性,尤其在阿里淘寶這種高頻交易場景中,具有非常廣泛地應用。第二代,拉模式,自研的專有訊息儲存。能夠媲美Kafka的吞吐效能,但考慮到淘寶的應用場景,尤其是其交易鏈路等高可靠場景,訊息引擎並沒有一位的追求吞吐,而是將穩定可靠放在首位。因為採用了長連線拉模式,在訊息的實時方面絲毫不遜推模式。在前兩代經歷了數年線上堪比工況的洗禮後,中介軟體團隊於2011年研發了以拉模式為主,兼有推模式的高效能、低延遲訊息引擎RocketMQ。並在2012年進行了開源,經歷了6年雙11核心交易鏈路檢驗,愈久彌堅。目前已經捐贈給阿帕奇基金會(ASF),有望成為繼ActiveMQ,Kafka之後,Apache社群第三個重量級分散式訊息引擎。時至今日,RocketMQ很好的服務了阿里集團大大小小上千個應用,在雙11當天,更有不可思議的萬億級訊息流轉,為集團大中臺的穩定發揮了舉足輕重的作用。

萬億級資料洪峰下的分散式訊息引擎

2. 低延遲可用性探索

疾風吹征帆,倏爾向空沒。千里在俄頃,三江坐超忽。—孟浩然

2.1低延遲與可用性

隨著Java語言生態的完善,JVM效能的提高,C和C++已經不再是低延遲場景唯一的選擇。本章節重點介紹RocketMQ在低延遲可用性方面的一些探索。
應用程式的效能度量標準一般從吞吐量和延遲兩方面考量。吞吐量是指程式在一段時間內能處理的請求數量。延遲是指端到端的響應時間。低延遲在不同的環境下有不同的定義,比如在聊天應用中低延遲可以定義為200ms內,在交易系統中定義為10ms內。相對於吞吐量,延遲會受到很多因素的影響,如CPU、網路、記憶體、作業系統等。
根據Little’s law,當延遲變高時,駐留在分散式系統中的請求會劇增,導致某些節點不可用,不可用的狀態甚至會擴散至其它節點,造成整個系統的服務能力喪失,這種場景又俗稱雪崩。所以打造低延遲的應用程式,對提升整個分散式系統可用性有很大的裨益。

2.2 低延遲探索之路

RocketMQ作為一款訊息引擎,最大的作用是非同步解耦和削峰填谷。一方面,分散式應用會利用RocketMQ來進行非同步解耦,應用程式可以自如地擴容和縮容。另一方面,當洪峰資料來臨時,大量的訊息需要堆積到RocketMQ中,後端程式可以根據自己的消費速度來進行資料的讀取。所以保證RocketMQ寫訊息鏈路的低延遲至關重要。
在今年雙11期間,天貓釋出了紅包火山的新玩法。該遊戲對延遲非常敏感,只能容忍50ms內的延遲,在壓測初期RocketMQ寫訊息出現了大量50~500ms的延遲,導致了在紅包噴發的高峰出現大量的失敗,嚴重影響前端業務。下圖為壓測紅包叢集在壓測時寫訊息延遲熱力圖統計。

萬億級資料洪峰下的分散式訊息引擎

作為一款純Java語言開發的訊息引擎,RocketMQ自主研發的儲存元件,依賴Page Cache進行加速和堆積,意味著它的效能會受到JVM、GC、核心、Linux記憶體管理機制、檔案IO等因素的影響。如下圖所示,一條訊息從客戶端傳送出,到最終落盤持久化,每個環節都有產生延遲的風險。通過對線上資料的觀察,RocketMQ寫訊息鏈路存在偶發的高達數秒的延遲。

萬億級資料洪峰下的分散式訊息引擎

2.2.1 JVM停頓

JVM(Java虛擬機器)在執行過程中會產生很多停頓,常見的有GC、JIT、取消偏向鎖(RevokeBias)、RedefineClasses(AOP)等。對應用程式影響最大的則是GC停頓。RocketMQ儘量避免Full GC,但Minor GC帶來的停頓是難以避免的。針對GC調優是一個很伽利略的問題,需要通過大量的測試來幫助應用程式調整GC引數,比如可以通過調整堆大小,GC的時機,優化資料結構等手段進行調優。
對於其它JVM停頓,可以通過-XX:+PrintGCApplicationStoppedTime將JVM停頓時間輸出到GC日誌中。通過-XX:+PrintSafepointStatistics -XX: PrintSafepointStatisticsCount=1輸出具體的停頓原因,並進行鍼對性的優化。比如在RocketMQ中發現取RevokeBias產生了大量的停頓,通過-XX:-UseBiasedLocking關閉了偏向鎖特性。
另外,GC日誌的輸出會發生檔案IO,有時候也會造成不必要的停頓,可以將GC日誌輸出到tmpfs(記憶體檔案系統)中,但tmpfs會消耗記憶體,為了避免記憶體被浪費可以使用-XX:+UseGCLogFileRotation滾動GC日誌。
除了GC日誌會產生檔案IO,JVM會將jstat命令需要的一些統計資料輸出到/tmp(hsperfdata)目錄下,可通過-XX:+PerfDisableSharedMem關閉該特性,並使用JMX來代替jstat。

2.2.2 鎖——同步的“利”器

作為一種臨界區的保護機制,鎖被廣泛用於多執行緒應用程式的開發中。但鎖是一把雙刃劍,過多或不正確的使用鎖會導致多執行緒應用的效能下降。
Java中的鎖預設採用的是非公平鎖,加鎖時不考慮排隊問題,直接嘗試獲取鎖,若獲取失敗自動進行排隊。非公平鎖會導致執行緒等待時間過長,延遲變高。倘若採取公平鎖,又會對應用帶來較大效能損失。
另一方面,同步會引起上下文切換,這會帶來一定的開銷。上下文切換一般是微秒級,但當執行緒數過多,競爭壓力大時,會產生數十毫秒級別的開銷。可通過LockSupport.park來模擬產生上下文切換進行測試。
為了避免鎖帶來的延遲,利用CAS原語將RocketMQ核心鏈路無鎖化,在降低延遲的同時顯著提高吞吐量。

2.2.3 記憶體——沒那麼快

受限於Linux的記憶體管理機制,應用程式訪問記憶體時有時候會產生高延遲。Linux中記憶體主要有匿名記憶體和Page Cache兩種。
Linux會用盡可能多的記憶體來做快取,大多數情形下,伺服器可用記憶體都較少。可用記憶體較少時,應用程式申請或者訪問新的記憶體頁會引發記憶體回收,當後臺記憶體回收的速度不及分配記憶體的速度時,會進入直接回收(Direct Reclaim),應用程式會自旋等待記憶體回收完畢,產生巨大的延遲,如下圖所示。

萬億級資料洪峰下的分散式訊息引擎

另一方面,核心也會回收匿名記憶體頁,匿名記憶體頁被換出後下一次訪問會產生檔案IO,導致延遲,如下圖所示。

萬億級資料洪峰下的分散式訊息引擎

上述兩種情況產生的延遲可以通過核心引數(vm.extra_free_kbytes和vm.swappiness)調優加以避免。
Linux對記憶體的管理一般是以頁為單位,一頁一般為4k大小,當在同一頁記憶體上產生讀寫競爭時,會產生延遲,對於這種情況,需要應用程式自行協調記憶體的訪問加以避免。

2.2.4 Page Cache——利與弊

Page Cache是檔案的快取,用於加速對檔案的讀寫,它為RocketMQ提供了更強大的堆積能力。RocketMQ將資料檔案對映到記憶體中,寫訊息的時候首先寫入Page Cache,並通過非同步刷盤的模式將訊息持久化(同時也支援同步刷盤),訊息可以直接從Page Cache中讀取,這也是業界分散式儲存產品通常採用的模式,如下圖所示:

萬億級資料洪峰下的分散式訊息引擎

該模式大多數情況讀寫速度都比較迅速,但當遇到作業系統進行髒頁回寫,記憶體回收,記憶體換入換出等情形時,會產生較大的讀寫延遲,造成儲存引擎偶發的高延遲。
針對這種現象,RocketMQ採用了多種優化技術,比如記憶體預分配,檔案預熱,mlock系統呼叫,讀寫分離等,來保證利用Page Cache優點的同時,消除其帶來的延遲。

2.3 優化成果

RocketMQ通過對上述情況的優化,成功消除了寫訊息高延遲的情形,並通過了今年雙11的考驗。優化後寫訊息耗時熱力圖如下圖所示。

萬億級資料洪峰下的分散式訊息引擎

優化後RocketMQ寫訊息延遲99.995%在1ms內,100%在100ms內,如下圖所示。

萬億級資料洪峰下的分散式訊息引擎

3 容量保障三大法寶

他強任他強,清風拂山崗。他橫任他橫,明月照大江。—九陽真經心法

有了低延遲的優化保障,並不意味著訊息引擎就可以高枕無憂。為了給應用帶來如絲般順滑的體驗,訊息引擎必須進行靈活的容量規劃。如何讓系統能夠在洶湧澎湃的流量洪峰面前談笑風生?降級、限流、熔斷三大法寶便有了用武之地。丟卒保車,以降級、暫停邊緣服務、元件為代價保障核心服務的資源,以系統不被突發流量擊垮為第一要務。正所謂,他強任他強,清風拂山崗。他橫任他橫,明月照大江!
從架構的穩定性角度看,在有限資源的情況下,所能提供的單位時間服務能力也是有限的。假如超過承受能力,可能會帶來整個服務的停頓,應用的Crash,進而可能將風險傳遞給服務呼叫方造成整個系統的服務能力喪失,進而引發雪崩。另外,根據排隊理論,具有延遲的服務隨著請求量的不斷提升,其平均響應時間也會迅速提升,為了保證服務的SLA,有必要控制單位時間的請求量。這就是限流為什麼愈發重要的原因。限流這個概念,在學術界又被稱之為Traffic Shaping。最早起源於網路通訊領域,典型的有漏桶(leaky bucket)演算法和令牌桶(token bucket)演算法。

萬億級資料洪峰下的分散式訊息引擎

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

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

萬億級資料洪峰下的分散式訊息引擎

無論是漏桶、令牌桶,抑或其它變種演算法,都可以看做是一種控制速度的限流,工程領域如Guava裡的RateLimiter,Netty裡的TrafficShaping等也都屬於此。除此之外,還有一種控制併發的限流模式,如作業系統裡的訊號量,JDK裡的Semaphore。
非同步解耦,削峰填谷,作為訊息引擎的看家本領,Try your best本身就是其最初的設計初衷(RPC、應用閘道器、容器等場景下,控制速度應成為流控首選)。但即便如此,一些必要的流控還是需要考量。不過與前面介紹的不同,RocketMQ中並沒有內建Guava、Netty等拆箱即用的速度流控元件。而是通過借鑑排隊理論,對其中的慢請求進行容錯處理。這裡的慢請求是指排隊等待時間以及服務時間超過某個閾值的請求。對於離線應用場景,容錯處理就是利用滑動視窗機制,通過緩慢縮小視窗的手段,來減緩從服務端拉的頻率以及訊息大小,降低對服務端的影響。而對於那些高頻交易,資料複製場景,則採取了快速失敗策略,既能預防應用連鎖的資源耗盡而引發的應用雪崩,又能有效降低服務端壓力,為端到端低延遲帶來可靠保障。
服務降級是一種典型的丟卒保車,二八原則實踐。而降級的手段也無外乎關閉,下線等“簡單粗暴”的操作。降級目標的選擇,更多來自於服務QoS的定義。訊息引擎早期對於降級的處理主要來自兩方面,一方面來自於使用者資料的收集,另一方面來自引擎元件的服務QoS設定。對於前者,通過運維管控系統推送應用自身QoS資料,一般會輸出如下表格。而引擎元件的服務QoS,如服務於訊息問題追溯的鏈路軌跡元件,對於核心功能來說,定級相對較低,可在洪峰到來之前提前關閉。

萬億級資料洪峰下的分散式訊息引擎

談到熔斷,不得不提經典的電力系統中的保險絲,當負載過大,或者電路發生故障或異常時,電流會不斷升高,為防止升高的電流有可能損壞電路中的某些重要器件或貴重器件,燒燬電路甚至造成火災。保險絲會在電流異常升高到一定的高度和熱度的時候,自身熔斷切斷電流,從而起到保護電路安全執行的作用。
同樣,在分散式系統中,如果呼叫的遠端服務或者資源由於某種原因無法使用時,沒有這種過載保護,就會導致請求的資源阻塞在伺服器上等待從而耗盡系統或者伺服器資源。很多時候剛開始可能只是系統出現了區域性的、小規模的故障,然而由於種種原因,故障影響的範圍越來越大,最終導致了全域性性的後果。而這種過載保護就是大家俗稱的熔斷器(Circuit Breaker)。Netflix公司為了解決該問題,開源了它們的熔斷解決方案Hystrix。

萬億級資料洪峰下的分散式訊息引擎

萬億級資料洪峰下的分散式訊息引擎

萬億級資料洪峰下的分散式訊息引擎

上述三幅圖,描述了系統從初始的健康狀態到高併發場景下阻塞在下游的某個關鍵依賴元件的場景。這種情況很容易誘發雪崩效應。而通過引入Hystrix的熔斷機制,讓應用快速失敗,繼而能夠避免最壞情況的發生。

萬億級資料洪峰下的分散式訊息引擎

借鑑Hystrix思路,中介軟體團隊自研了一套訊息引擎熔斷機制。在大促壓測備戰期間,曾經出現過由於機器硬體裝置導致服務不可用。如果採用常規的容錯手段,是需要等待30秒時間,不可用機器才能從列表裡被摘除。但通過這套熔斷機制,能在毫秒範圍內識別並隔離異常服務。進一步提升了引擎的可用性。

4. 高可用解決方案

昔之善戰者,先為不可勝,以待敵之可勝。不可勝在己,可勝在敵。故善戰者,能為不可勝,不能使敵之必可勝。故曰:勝可知,而不可為。—孫武

雖然有了容量保障的三大法寶作為依託,但隨著訊息引擎叢集規模的不斷上升,到達一定程度後,叢集中機器故障的可能性隨之提高,嚴重降低訊息的可靠性以及系統的可用性。與此同時,基於多機房部署的叢集模式也會引發機房斷網,進一步降低訊息系統的可用性。為此,阿里中介軟體(Aliware)重點推出了基於多副本的高可用解決方案,動態識別機器故障、機房斷網等災難場景,實現故障自動恢復;整個恢復過程對使用者透明,無需運維人員干預,極大地提升了訊息儲存的可靠性,保障了整個叢集的高可用性。
高可用性幾乎是每個分散式系統在設計時必須要考慮的一個重要特性,在遵循CAP原則(即:一致性、可用性和分割槽容錯性三者無法在分散式系統中被同時滿足,並且最多隻能滿足其中兩個)基礎上,業界也提出了一些針對分散式系統通用的高可用解決方案,如下圖所示:

萬億級資料洪峰下的分散式訊息引擎

其中,行代表了分散式系統中通用的高可用解決方案,包括冷備、Master/Slave、Master/Master、兩階段提交以及基於Paxos演算法的解決方案;列代表了分散式系統所關心的各項指標,包括資料一致性、事務支援程度、資料延遲、系統吞吐量、資料丟失可能性、故障自動恢復方式。
從圖中可以看出,不同的解決方案對各項指標的支援程度各有側重。基於CAP原則,很難設計出一種高可用方案能同時夠滿足所有指標的最優值,以Master/Slave為例,一般滿足如下幾個特性:
1) Slave是Master的備份,可以根據資料的重要程度設定Slave的個數。
資料寫請求命中Master,讀請求可命中Master或者Slave。
2) 寫請求命中Master之後,資料可通過同步或者非同步的方式從Master複製到Slave上;其中同步複製模式需要保證Master和Slave均寫成功後才反饋給客戶端成功;非同步複製模式只需要保證Master寫成功即可反饋給客戶端成功。
資料通過同步或者非同步方式從Master複製到Slave上,因此Master/Slave結構至少能保證資料的最終一致性;非同步複製模式下,資料在Master寫成功後即可反饋給客戶端成功,因此係統擁有較低的延遲和較高的吞吐量,但同時會帶來Master故障丟資料的可能性;如期望非同步複製模式下Master故障時資料仍不丟,Slave只能以Read-Only的方式等待Master的恢復,即延長了系統的故障恢復時間。相反,Master/Slave結構中的同步複製模式會以增大資料寫入延遲、降低系統吞吐量的代價來保證機器故障時資料不丟,同時降低系統故障恢復時間。

5. RocketMQ高可用架構

RocketMQ基於原有多機房部署的叢集模式,利用分散式鎖和通知機制,藉助Controller元件,設計並實現了Master/Slave結構的高可用架構,如下圖所示:

萬億級資料洪峰下的分散式訊息引擎

其中,Zookeeper作為分散式排程框架,需要至少在A、B、C三個機房部署以保證其高可用,併為RocketMQ高可用架構提供如下功能:
1) 維護持久節點(PERSISTENT),儲存主備狀態機;
2) 維護臨時節點(EPHEMERAL),儲存RocketMQ的當前狀態;
3) 當主備狀態機、服務端當前狀態發生變更時,通知對應的觀察者。
RocketMQ以Master/Slave結構實現多機房對等部署,訊息的寫請求會命中Master,然後通過同步或者非同步方式複製到Slave上進行持久化儲存;訊息的讀請求會優先命中Master,當訊息堆積導致磁碟壓力大時,讀請求轉移至Slave。
RocketMQ直接與Zookeeper進行互動,體現在:
1) 以臨時節點的方式向Zookeeper彙報當前狀態;
2) 作為觀察者監聽Zookeeper上主備狀態機的變更。當發現主備狀態機變化時,根據最新的狀態機更改當前狀態;
RocketMQ HA Controller是訊息引擎高可用架構中降低系統故障恢復時間的無狀態元件,在A、B、C三個機房分散式部署,其主要職責體現在:
1) 作為觀察者監聽Zookeeper 上RocketMQ當前狀態的變更;
2) 根據叢集的當前狀態,控制主備狀態機的切換並向Zookeeper彙報最新主備狀態機。
出於對系統複雜性以及訊息引擎本身對CAP原則適配的考慮,RocketMQ高可用架構的設計採用了Master/Slave結構,在提供低延遲、高吞吐量訊息服務的基礎上,採用主備同步複製的方式避免故障時訊息的丟失。資料同步過程中,通過維護一個遞增的全域性唯一SequenceID來保證資料強一致。同時引入故障自動恢復機制以降低故障恢復時間,提升系統的可用性。

5.1 可用性評估

系統可用性(Availability)是資訊工業界用來衡量一個資訊系統提供持續服務的能力,它表示的是在給定時間區間內系統或者系統某一能力在特定環境中能夠正常工作的概率。簡單地說, 可用性是平均故障間隔時間(MTBF)除以平均故障間隔時間(MTBF)和平均故障修復時間(MTTR)之和所得的結果, 即:

萬億級資料洪峰下的分散式訊息引擎

通常業界習慣用N個9來表徵系統可用性,比如99.9%代表3個9的可用性,意味著全年不可用時間在8.76小時以內;99.999%代表5個9的可用性,意味著全年不可用時間必須保證在5.26分鐘以內,缺少故障自動恢復機制的系統將很難達到5個9的高可用性。

5.2 RocketMQ 高可用保障

通過可用性計算公式可以看出,要提升系統的可用性,需要在保障系統健壯性以延長平均無故障時間的基礎上,進一步加強系統的故障自動恢復能力以縮短平均故障修復時間。RocketMQ高可用架構設計並實現了Controller元件,按照單主狀態、非同步複製狀態、半同步狀態以及最終的同步複製狀態的有限狀態機進行轉換。在最終的同步複製狀態下,Master和Slave任一節點故障時,其它節點能夠在秒級時間內切換到單主狀態繼續提供服務。相比於之前人工介入重啟來恢復服務,RokcetMQ高可用架構賦予了系統故障自動恢復的能力,能極大縮短平均故障恢復時間,提升系統的可用性。

下圖描述了RocketMQ高可用架構中有限狀態機的轉換:

萬億級資料洪峰下的分散式訊息引擎

1) 第一個節點啟動後,Controller控制狀態機切換為單主狀態,通知啟動節點以Master角色提供服務。
2) 第二個節點啟動後,Controller控制狀態機切換成非同步複製狀態。Master通過非同步方式向Slave複製資料。
3) 當Slave的資料即將趕上Master,Controller控制狀態機切換成半同步狀態,此時命中Master的寫請求會被Hold住,直到Master以非同步方式向Slave複製了所有差異的資料。
4) 當半同步狀態下Slave的資料完全趕上Master時,Controller控制狀態機切換成同步複製模式,Mater開始以同步方式向Slave複製資料。該狀態下任一節點出現故障,其它節點能夠在秒級內切換到單主狀態繼續提供服務。
Controller元件控制RocketMQ按照單主狀態,非同步複製狀態,半同步狀態,同步複製狀態的順序進行狀態機切換。中間狀態的停留時間與主備之間的資料差異以及網路頻寬有關,但最終都會穩定在同步複製狀態下。

展望

雖然經歷了這麼多年線上堪比工況的苛刻檢驗,阿里中介軟體訊息引擎仍然存在著優化空間,如團隊正嘗試通過優化儲存演算法、跨語言呼叫等策略進一步降低訊息低延遲儲存。面對移動物聯網、大資料、VR等新興場景,面對席捲全球的開放與商業化生態,團隊開始著手打造第4代訊息引擎,多級協議QoS,跨網路、跨終端、跨語言支援,面向線上應用更低的響應時間,面向離線應用更高的吞吐,秉持取之於開源,回饋於開源的思想,相信RocektMQ朝著更健康的生態發展。

參考文獻

[1]Ryan Barrett. http://snarfed.org/transactions_across_datacenters_io.html
[2]http://www.slideshare.net/vimal25792/leaky-bucket-tocken-buckettraffic-shaping
[3]http://systemdesigns.blogspot.com/2015/12/rate-limiter.html
[4]Little J D C, Graves S C. Little’s law[M]//Building intuition. Springer US, 2008: 81-100.
[5]https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html-single/Performance_Tuning_Guide/index.html
[6]http://highscalability.com/blog/2012/3/12/google-taming-the-long-latency-tail-when-more-machines-equal.html
[7]https://www.azul.com/files/EnablingJavaInLatencySensitiveEnvs_DotCMSBootcamp_Nashville_23Oct20141.pdf

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31562044/viewspace-2285474/,如需轉載,請註明出處,否則將追究法律責任。

相關文章