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

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

前言

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

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的延遲,致使了在紅包噴發的高峯出現大量的失敗,嚴重影響前端業務。下圖爲壓測紅包集羣在壓測時寫消息延遲熱力圖統計。vim

做爲一款純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。api

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

 

 

 

轉載:http://jm.taobao.org/2017/01/26/20170126/

相關文章
相關標籤/搜索