天天看點

【雙11背後的技術】萬億級資料洪峰下的分布式消息引擎 

本文作者:馮嘉、誓嘉、塵央、牟羽 

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

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

【雙11背後的技術】萬億級資料洪峰下的分布式消息引擎 

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

随着java語言生态的完善,jvm性能的提高,c和c++已經不再是低延遲場景唯一的選擇。本章節重點介紹rocketmq在低延遲可用性方面的一些探索。

應用程式的性能度量标準一般從吞吐量和延遲兩方面考量。吞吐量是指程式在一段時間内能處理的請求數量。延遲是指端到端的響應時間。低延遲在不同的環境下有不同的定義,比如在聊天應用中低延遲可以定義為200ms内,在交易系統中定義為10ms内。相對于吞吐量,延遲會受到很多因素的影響,如cpu、網絡、記憶體、作業系統等。

根據little's law,當延遲變高時,駐留在分布式系統中的請求會劇增,導緻某些節點不可用,不可用的狀态甚至會擴散至其它節點,造成整個系統的服務能力喪失,這種場景又俗稱雪崩。是以打造低延遲的應用程式,對提升整個分布式系統可用性有很大的裨益。

rocketmq作為一款消息引擎,最大的作用是異步解耦和削峰填谷。一方面,分布式應用會利用rocketmq來進行異步解耦,應用程式可以自如地擴容和縮容。另一方面,當洪峰資料來臨時,大量的消息需要堆積到rocketmq中,後端程式可以根據自己的消費速度來進行資料的讀取。是以保證rocketmq寫消息鍊路的低延遲至關重要。

在今年雙11期間,天貓釋出了紅包火山的新玩法。該遊戲對延遲非常敏感,隻能容忍50ms内的延遲,在壓測初期rocketmq寫消息出現了大量50~500ms的延遲,導緻了在紅包噴發的高峰出現大量的失敗,嚴重影響前端業務。下圖為壓測紅包叢集在壓測時寫消息延遲熱力圖統計。

【雙11背後的技術】萬億級資料洪峰下的分布式消息引擎 

作為一款純java語言開發的消息引擎,rocketmq自主研發的存儲元件,依賴page cache進行加速和堆積,意味着它的性能會受到jvm、gc、核心、linux記憶體管理機制、檔案io等因素的影響。如下圖所示,一條消息從用戶端發送出,到最終落盤持久化,每個環節都有産生延遲的風險。通過對線上資料的觀察,rocketmq寫消息鍊路存在偶發的高達數秒的延遲。

【雙11背後的技術】萬億級資料洪峰下的分布式消息引擎 

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。

作為一種臨界區的保護機制,鎖被廣泛用于多線程應用程式的開發中。但鎖是一把雙刃劍,過多或不正确的使用鎖會導緻多線程應用的性能下降。

java中的鎖預設采用的是非公平鎖,加鎖時不考慮排隊問題,直接嘗試擷取鎖,若擷取失敗自動進行排隊。非公平鎖會導緻線程等待時間過長,延遲變高。倘若采取公平鎖,又會對應用帶來較大性能損失。

另一方面,同步會引起上下文切換,這會帶來一定的開銷。上下文切換一般是微秒級,但當線程數過多,競争壓力大時,會産生數十毫秒級别的開銷。可通過locksupport.park來模拟産生上下文切換進行測試。

為了避免鎖帶來的延遲,利用cas原語将rocketmq核心鍊路無鎖化,在降低延遲的同時顯著提高吞吐量。

受限于linux的記憶體管理機制,應用程式通路記憶體時有時候會産生高延遲。linux中記憶體主要有匿名記憶體和page cache兩種。

linux會用盡可能多的記憶體來做緩存,大多數情形下,伺服器可用記憶體都較少。可用記憶體較少時,應用程式申請或者通路新的記憶體頁會引發記憶體回收,當背景記憶體回收的速度不及配置設定記憶體的速度時,會進入直接回收(direct reclaim),應用程式會自旋等待記憶體回收完畢,産生巨大的延遲,如下圖所示。

【雙11背後的技術】萬億級資料洪峰下的分布式消息引擎 

另一方面,核心也會回收匿名記憶體頁,匿名記憶體頁被換出後下一次通路會産生檔案io,導緻延遲,如下圖所示。

【雙11背後的技術】萬億級資料洪峰下的分布式消息引擎 

上述兩種情況産生的延遲可以通過核心參數(vm.extra_free_kbytes和vm.swappiness)調優加以避免。

linux對記憶體的管理一般是以頁為機關,一頁一般為4k大小,當在同一頁記憶體上産生讀寫競争時,會産生延遲,對于這種情況,需要應用程式自行協調記憶體的通路加以避免。

page cache是檔案的緩存,用于加速對檔案的讀寫,它為rocketmq提供了更強大的堆積能力。rocketmq将資料檔案映射到記憶體中,寫消息的時候首先寫入page cache,并通過異步刷盤的模式将消息持久化(同時也支援同步刷盤),消息可以直接從page cache中讀取,這也是業界分布式存儲産品通常采用的模式,如下圖所示:

【雙11背後的技術】萬億級資料洪峰下的分布式消息引擎 

該模式大多數情況讀寫速度都比較迅速,但當遇到作業系統進行髒頁回寫,記憶體回收,記憶體換入換出等情形時,會産生較大的讀寫延遲,造成存儲引擎偶發的高延遲。

針對這種現象,rocketmq采用了多種優化技術,比如記憶體預配置設定,檔案預熱,mlock系統調用,讀寫分離等,來保證利用page cache優點的同時,消除其帶來的延遲。

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

【雙11背後的技術】萬億級資料洪峰下的分布式消息引擎 

優化後rocketmq寫消息延遲99.995%在1ms内,100%在100ms内,如下圖所示。

【雙11背後的技術】萬億級資料洪峰下的分布式消息引擎 

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

有了低延遲的優化保障,并不意味着消息引擎就可以高枕無憂。為了給應用帶來如絲般順滑的體驗,消息引擎必須進行靈活的容量規劃。如何讓系統能夠在洶湧澎湃的流量洪峰面前談笑風生?降級、限流、熔斷三大法寶便有了用武之地。丢卒保車,以降級、暫停邊緣服務、元件為代價保障核心服務的資源,以系統不被突發流量擊垮為第一要務。正所謂,他強任他強,清風拂山崗。他橫任他橫,明月照大江!

從架構的穩定性角度看,在有限資源的情況下,所能提供的機關時間服務能力也是有限的。假如超過承受能力,可能會帶來整個服務的停頓,應用的crash,進而可能将風險傳遞給服務調用方造成整個系統的服務能力喪失,進而引發雪崩。另外,根據排隊理論,具有延遲的服務随着請求量的不斷提升,其平均響應時間也會迅速提升,為了保證服務的sla,有必要控制機關時間的請求量。這就是限流為什麼愈發重要的原因。限流這個概念,在學術界又被稱之為traffic shaping。最早起源于網絡通訊領域,典型的有漏桶(leaky bucket)算法和令牌桶(token bucket)算法。

【雙11背後的技術】萬億級資料洪峰下的分布式消息引擎 

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

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

【雙11背後的技術】萬億級資料洪峰下的分布式消息引擎 

無論是漏桶、令牌桶,抑或其它變種算法,都可以看做是一種控制速度的限流,工程領域如guava裡的ratelimiter,netty裡的trafficshaping等也都屬于此。除此之外,還有一種控制并發的限流模式,如作業系統裡的信号量,jdk裡的semaphore。

異步解耦,削峰填谷,作為消息引擎的看家本領,try your best本身就是其最初的設計初衷(rpc、應用網關、容器等場景下,控制速度應成為流控首選)。但即便如此,一些必要的流控還是需要考量。不過與前面介紹的不同,rocketmq中并沒有内置guava、netty等拆箱即用的速度流控元件。而是通過借鑒排隊理論,對其中的慢請求進行容錯處理。這裡的慢請求是指排隊等待時間以及服務時間超過某個門檻值的請求。對于離線應用場景,容錯處理就是利用滑動視窗機制,通過緩慢縮小視窗的手段,來減緩從服務端拉的頻率以及消息大小,降低對服務端的影響。而對于那些高頻交易,資料複制場景,則采取了快速失敗政策,既能預防應用連鎖的資源耗盡而引發的應用雪崩,又能有效降低服務端壓力,為端到端低延遲帶來可靠保障。

服務降級是一種典型的丢卒保車,二八原則實踐。而降級的手段也無外乎關閉,下線等“簡單粗暴”的操作。降級目标的選擇,更多來自于服務qos的定義。消息引擎早期對于降級的處理主要來自兩方面,一方面來自于使用者資料的收集,另一方面來自引擎元件的服務qos設定。對于前者,通過運維管控系統推送應用自身qos資料,一般會輸出如下表格。而引擎元件的服務qos,如服務于消息問題追溯的鍊路軌迹元件,對于核心功能來說,定級相對較低,可在洪峰到來之前提前關閉。

【雙11背後的技術】萬億級資料洪峰下的分布式消息引擎 

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

同樣,在分布式系統中,如果調用的遠端服務或者資源由于某種原因無法使用時,沒有這種過載保護,就會導緻請求的資源阻塞在伺服器上等待進而耗盡系統或者伺服器資源。很多時候剛開始可能隻是系統出現了局部的、小規模的故障,然而由于種種原因,故障影響的範圍越來越大,最終導緻了全局性的後果。而這種過載保護就是大家俗稱的熔斷器(circuit breaker)。netflix公司為了解決該問題,開源了它們的熔斷解決方案hystrix。

【雙11背後的技術】萬億級資料洪峰下的分布式消息引擎 
【雙11背後的技術】萬億級資料洪峰下的分布式消息引擎 
【雙11背後的技術】萬億級資料洪峰下的分布式消息引擎 

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

【雙11背後的技術】萬億級資料洪峰下的分布式消息引擎 

借鑒hystrix思路,中間件團隊自研了一套消息引擎熔斷機制。在大促壓測備戰期間,曾經出現過由于機器硬體裝置導緻服務不可用。如果采用正常的容錯手段,是需要等待30秒時間,不可用機器才能從清單裡被摘除。但通過這套熔斷機制,能在毫秒範圍内識别并隔離異常服務。進一步提升了引擎的可用性。

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

雖然有了容量保障的三大法寶作為依托,但随着消息引擎叢集規模的不斷上升,到達一定程度後,叢集中機器故障的可能性随之提高,嚴重降低消息的可靠性以及系統的可用性。與此同時,基于多機房部署的叢集模式也會引發機房斷網,進一步降低消息系統的可用性。為此,阿裡中間件(aliware)重點推出了基于多副本的高可用解決方案,動态識别機器故障、機房斷網等災難場景,實作故障自動恢複;整個恢複過程對使用者透明,無需運維人員幹預,極大地提升了消息存儲的可靠性,保障了整個叢集的高可用性。

高可用性幾乎是每個分布式系統在設計時必須要考慮的一個重要特性,在遵循cap原則(即:一緻性、可用性和分區容錯性三者無法在分布式系統中被同時滿足,并且最多隻能滿足其中兩個)基礎上,業界也提出了一些針對分布式系統通用的高可用解決方案,如下圖所示:

【雙11背後的技術】萬億級資料洪峰下的分布式消息引擎 

其中,行代表了分布式系統中通用的高可用解決方案,包括冷備、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結構中的同步複制模式會以增大資料寫入延遲、降低系統吞吐量的代價來保證機器故障時資料不丢,同時降低系統故障恢複時間。

rocketmq基于原有多機房部署的叢集模式,利用分布式鎖和通知機制,借助controller元件,設計并實作了master/slave結構的高可用架構,如下圖所示:

【雙11背後的技術】萬億級資料洪峰下的分布式消息引擎 

其中,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來保證資料強一緻。同時引入故障自動恢複機制以降低故障恢複時間,提升系統的可用性。

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

【雙11背後的技術】萬億級資料洪峰下的分布式消息引擎 

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

通過可用性計算公式可以看出,要提升系統的可用性,需要在保障系統健壯性以延長平均無故障時間的基礎上,進一步加強系統的故障自動恢複能力以縮短平均故障修複時間。rocketmq高可用架構設計并實作了controller元件,按照單主狀态、異步複制狀态、半同步狀态以及最終的同步複制狀态的有限狀态機進行轉換。在最終的同步複制狀态下,master和slave任一節點故障時,其它節點能夠在秒級時間内切換到單主狀态繼續提供服務。相比于之前人工介入重新開機來恢複服務,rokcetmq高可用架構賦予了系統故障自動恢複的能力,能極大縮短平均故障恢複時間,提升系統的可用性。

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

【雙11背後的技術】萬億級資料洪峰下的分布式消息引擎 

第一個節點啟動後,controller控制狀态機切換為單主狀态,通知啟動節點以master角色提供服務。

第二個節點啟動後,controller控制狀态機切換成異步複制狀态。master通過異步方式向slave複制資料。

當slave的資料即将趕上master,controller控制狀态機切換成半同步狀态,此時命中master的寫請求會被hold住,直到master以異步方式向slave複制了所有差異的資料。

當半同步狀态下slave的資料完全趕上master時,controller控制狀态機切換成同步複制模式,mater開始以同步方式向slave複制資料。該狀态下任一節點出現故障,其它節點能夠在秒級内切換到單主狀态繼續提供服務。

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

雖然經曆了這麼多年線上堪比工況的苛刻檢驗,阿裡中間件消息引擎仍然存在着優化空間,如團隊正嘗試通過優化存儲算法、跨語言調用等政策進一步降低消息低延遲存儲。面對移動物聯網、大資料、vr等新興場景,面對席卷全球的開放與商業化生态,團隊開始着手打造第4代消息引擎,多級協定qos,跨網絡、跨終端、跨語言支援,面向線上應用更低的響應時間,面向離線應用更高的吞吐,秉持取之于開源,回饋于開源的思想,相信rocektmq朝着更健康的生态發展。

[4]little j d c, graves s c. little's law[m]//building intuition. springer us, 2008: 81-100.