萬億級數據洪峯下的分佈式消息引擎

作者:馮嘉 誓嘉 塵央 牟羽
原文:http://jm.taobao.org/2017/01/26/20170126/

前言

通過簡單回顧阿里中間件(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朝着更健康的生態發展。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章