RocketMQ 端云一體化設(shè)計與實踐
作者 | 悟幻
一體化布景
不止于分發(fā)
咱們都知道以 RocketMQ 為代表的音訊(行列)起源于不同應(yīng)用服務(wù)之間的異步解耦通訊,與以 Dubbo 為代表的 RPC 類服務(wù)通訊一同承載了分布式體系(服務(wù))之間的通訊場景,所以服務(wù)間的音訊分發(fā)是音訊的根底訴求。可是咱們看到,在音訊(行列)這個范疇,近些年咱們業(yè)界有個很重要的趨勢,便是依據(jù)音訊這份數(shù)據(jù)可以擴(kuò)展到流批核算、事情驅(qū)動等不同場景,如 RocketMQ-streams,Kafka-Streams、Rabbit-Streams 等等。
不止于服務(wù)端
傳統(tǒng)的音訊行列 MQ 首要應(yīng)用于服務(wù)(端)之間的音訊通訊,比方電商范疇的買賣音訊、付出音訊、物流音訊等等??墒窃谝粲嵾@個大類下,還有一個非常重要且常見的音訊范疇,即終端音訊。音訊的本質(zhì)便是發(fā)送和承受,終端和服務(wù)端并沒有本質(zhì)上的大差異。
一體化價值
假如可以有一個一致的音訊體系(產(chǎn)品)來提供多場景核算(如 stream、event)、多場景(IoT、APP)接入,其實是非常有價值的,由于音訊也是一種重要數(shù)據(jù),數(shù)據(jù)假如只存在一個體系內(nèi),可以最大地下降存儲本錢,一起可以有效地避免數(shù)據(jù)因在不同體系間同步帶來的一致性難題。
終端音訊剖析
本文將首要描繪的是終端音訊和服務(wù)端音訊一體化規(guī)劃與實踐問題,所以首先咱們對面向終端的這一大類音訊做一下根本剖析。
場景介紹
近些年,咱們看到跟著智能家居、工業(yè)互聯(lián)而鼓起的面向 IoT 設(shè)備類的音訊正在呈爆破式增加,而現(xiàn)已發(fā)展十余年的移動互聯(lián)網(wǎng)的手機(jī) APP 端音訊仍然是數(shù)量級龐大。面向終端設(shè)備的音訊數(shù)量級比傳統(tǒng)服務(wù)端的音訊要大許多量級,并仍然在快速增加。
特性剖析
雖然無論是終端音訊仍是服務(wù)端音訊,其本質(zhì)都是音訊的發(fā)送和承受,可是終端場景仍是有和服務(wù)端不太相同的特色,下面扼要剖析一下:
- 輕量
服務(wù)端一般都是運用很重的客戶端 SDK 封裝了許多功用和特性,可是終端由于運轉(zhuǎn)環(huán)境受限且雜亂有必要運用輕量簡潔的客戶端 SDK。
- 規(guī)范協(xié)議
服務(wù)端正是由于有了重量級客戶端 SDK,其封裝了包括協(xié)議通訊在內(nèi)的全部功用,乃至可以弱化協(xié)議的存在,運用者無須感知,而終端場景由于要支撐各類雜亂的設(shè)備和場景接入,有必要要有個規(guī)范協(xié)議界說。
- P2P
服務(wù)端音訊假如一臺服務(wù)器處理失利可以由別的一臺服務(wù)器處理成功即可,而終端音訊有必要清晰發(fā)給詳細(xì)終端,若該終端處理失利則有必要一直重試發(fā)送該終端直到成功,這個和服務(wù)端很不相同。
- 播送比
服務(wù)端音訊比方買賣體系發(fā)送了一條訂單音訊,或許有如營銷、庫存、物流等幾個體系感興趣,而終端場景比方群聊、直播或許成千上萬的終端設(shè)備或用戶需求收到。
- 海量接入
終端場景接入的是終端設(shè)備,而服務(wù)端接入的便是服務(wù)器,前者在量級上肯定遠(yuǎn)大于后者。
架構(gòu)與模型
音訊根底剖析
完成一體化前咱們先從理論上剖析一下問題和可行性。咱們知道,無論是終端音訊仍是服務(wù)端音訊,其實便是一種通訊方法,從通訊的層面看要處理的根底問題簡略總結(jié)便是:協(xié)議、匹配、觸達(dá)。
- 協(xié)議
協(xié)議便是界說了一個交流言語頻道,通訊兩邊可以聽懂內(nèi)容語義。在終端場景,現(xiàn)在業(yè)界廣泛運用的是 MQTT 協(xié)議,起源于物聯(lián)網(wǎng) IoT 場景,OASIS 聯(lián)盟界說的規(guī)范的開放式協(xié)議。
MQTT 協(xié)議界說了是一個 Pub/Sub 的通訊模型,這個與 RocketMQ 相似的,不過其在訂閱方法上比較靈敏,可以支撐多級 Topic 訂閱(如 “/t/t1/t2”),可以支撐通配符訂閱(如 “/t/t1/+”)
- 匹配
匹配便是發(fā)送一條音訊后要找到一切的承受者,這個匹配查找進(jìn)程是不可或缺的。
在 RocketMQ 里邊實際上有這個相似的匹配進(jìn)程,其經(jīng)過將某個 Queue 經(jīng)過 rebalance 方法分配到消費組內(nèi)某臺機(jī)器上,音訊經(jīng)過 Queue 就直接對應(yīng)上了消費機(jī)器,再經(jīng)過訂閱過濾(Tag 或 SQL)進(jìn)行精準(zhǔn)匹配消費者。之所以經(jīng)過 Queue 就可以匹配消費機(jī)器,是由于服務(wù)端場景音訊并不需求清晰指定某臺消費機(jī)器,一條音訊可以放到恣意 Queue 里邊,并且恣意一臺消費機(jī)器對應(yīng)這個 Queue 都可以,音訊不需求清晰匹配消費機(jī)器。
而在終端場景下,一條音訊有必要清晰指定某個承受者(設(shè)備),有必要精確找到一切承受者,并且終端設(shè)備一般只會連到某個后端服務(wù)節(jié)點即單連接,和音訊發(fā)生的節(jié)點不是同一個,有必要有個較雜亂的匹配查找方針的進(jìn)程,還有如 MQTT 通配符這種更靈敏的匹配特性。
- 觸達(dá)
觸達(dá)即經(jīng)過匹配查找后找到一切的承受者方針,需求將音訊以某種牢靠方法發(fā)給承受者。常見的觸發(fā)方法有兩種:Push、Pull。Push,即服務(wù)端自動推送音訊給終端設(shè)備,自動權(quán)在服務(wù)端側(cè),終端設(shè)備經(jīng)過 ACK 來反饋音訊是否成功收到或處理,服務(wù)端需求依據(jù)終端是否回來 ACK 來決議是否重投。Pull,即終端設(shè)備自動來服務(wù)端獲取其一切音訊,自動權(quán)在終端設(shè)備側(cè),一般經(jīng)過位點 Offset 來順次獲取音訊,RocketMQ 便是這種音訊獲取方法。
對比兩種方法,咱們可以看到 Pull 方法需求終端設(shè)備自動辦理音訊獲取邏輯,這個邏輯其實有必定的雜亂性(可以參閱 RocketMQ 的客戶端辦理邏輯),而終端設(shè)備運轉(zhuǎn)環(huán)境和條件都很雜亂,不太習(xí)慣較雜亂的 Pull 邏輯完成,比較適合被迫的 Push 方法。
別的,終端音訊有一個很重要的差異是牢靠性確保的 ACK 有必要是詳細(xì)到一個終端設(shè)備的,而服務(wù)端音訊的牢靠性在于只要有一臺消費者機(jī)器成功處理即可,不太關(guān)心是哪臺消費者機(jī)器,音訊的牢靠性 ACK 標(biāo)識可以會集在消費組維度,而終端音訊的牢靠性 ACK 標(biāo)識需求詳細(xì)離散到終端設(shè)備維度。簡略地說,一個是客戶端設(shè)備維度的 Retry 行列,一個是消費組維度的 Retry 行列。
模型與組件
依據(jù)前面的音訊根底一般性剖析,咱們來規(guī)劃音訊模型,首要是要處理好匹配查找和牢靠觸達(dá)兩個中心問題。
- 行列模型
音訊可以牢靠性觸達(dá)的條件是要牢靠存儲,音訊存儲的目的是為了讓承受者能獲取到音訊,承受者一般有兩種音訊檢索維度:
1)依據(jù)訂閱的主題 Topic 去查找音訊;
2)依據(jù)訂閱者 ID 去查找音訊。這個便是業(yè)界常說的擴(kuò)大模型:讀擴(kuò)大、寫擴(kuò)大。
讀擴(kuò)大:即音訊按 Topic 進(jìn)行存儲,承受者依據(jù)訂閱的 Topic 列表去相應(yīng)的 Topic 行列讀取音訊。
寫擴(kuò)大:即音訊別離寫到一切訂閱的承受者行列中,每個承受者讀取自己的客戶端行列。
可以看到讀擴(kuò)大場景下音訊只寫一份,寫到 Topic 維度的行列,但承受者讀取時需求依照訂閱的 Topic 列表多次讀取,而寫擴(kuò)大場景下音訊要寫多份,寫到一切承受者的客戶端行列里邊,明顯存儲本錢較大,但承受者讀取簡略,只需讀取自己客戶端一個行列即可。
咱們采用的讀擴(kuò)大為主,寫擴(kuò)大為輔的策略,由于存儲的本錢和效率對用戶的體感最明顯。寫多份不只加大了存儲本錢,一起也對功能和數(shù)據(jù)精確一致性提出了應(yīng)戰(zhàn)??墒怯幸粋€當(dāng)?shù)卦蹅冞\用了寫擴(kuò)大模式,便是通配符匹配,由于承受者訂閱的是通配符和音訊的 Topic 不是相同的內(nèi)容,承受者讀音訊時無法反推出音訊的 Topic,因此需求在音訊發(fā)送時依據(jù)通配符的訂閱多寫一個通配符行列,這樣承受者直接可以依據(jù)其訂閱的通配符行列讀取音訊。
上圖描繪的承受咱們的行列存儲模型,音訊可以來自各個接入場景(如服務(wù)端的 MQ/AMQP,客戶端的 MQTT),但只會寫一份存到 commitlog 里邊,然后分發(fā)出多個需求場景的行列索引(ConsumerQueue),如服務(wù)端場景(MQ/AMQP)可以依照一級 Topic 行列進(jìn)行傳統(tǒng)的服務(wù)端消費,客戶端 MQTT 場景可以依照 MQTT 多級 Topic 以及通配符訂閱進(jìn)行消費音訊。
這樣的一個行列模型就可以一起支撐服務(wù)端和終端場景的接入和音訊收發(fā),抵達(dá)一體化的方針。
- 推拉模型
介紹了底層的行列存儲模型后,咱們再詳細(xì)描繪一下匹配查找和牢靠觸達(dá)是怎樣做的。
上圖展示的是一個推拉模型,圖中的 P 節(jié)點是一個協(xié)議網(wǎng)關(guān)或 broker 插件,終端設(shè)備經(jīng)過 MQTT 協(xié)議連到這個網(wǎng)關(guān)節(jié)點。音訊可以來自多種場景(MQ/AMQP/MQTT)發(fā)送過來,存到 Topic 行列后會有一個 notify 邏輯模塊來實時感知這個新音訊抵達(dá),然后會生成音訊事情(便是音訊的 Topic 稱號),將該事情推送至網(wǎng)關(guān)節(jié)點,網(wǎng)關(guān)節(jié)點依據(jù)其連上的終端設(shè)備訂閱狀況進(jìn)行內(nèi)部匹配,找到哪些終端設(shè)備能匹配上,然后會觸發(fā) pull 懇求去存儲層讀取音訊再推送終端設(shè)備。
一個重要問題,便是 notify 模塊怎樣知道一條音訊在哪些網(wǎng)關(guān)節(jié)點上面的終端設(shè)備感興趣,這個其實便是要害的匹配查找問題。一般有兩種方法:1)簡略的播送事情;2)會集存儲在線訂閱聯(lián)系(如圖中的 lookup 模塊),然后進(jìn)行匹配查找再精準(zhǔn)推送。事情播送機(jī)制看起來有擴(kuò)展性問題,可是其實功能并不差,由于咱們推送的數(shù)據(jù)很小便是 Topic 稱號,并且相同 Topic 的音訊事情可以合并成一個事情,咱們線上便是默許采用的這個方法。會集存儲在線訂閱聯(lián)系,這個也是常見的一種做法,如保存到 Rds、Redis 等,但要確保數(shù)據(jù)的實時一致性也有難度,并且要進(jìn)行匹配查找對整個音訊的實時鏈路 RT 開支也會有必定的影響。
牢靠觸達(dá)及實時性這塊,上圖的推拉進(jìn)程中首先是經(jīng)過事情告訴機(jī)制來實時奉告網(wǎng)關(guān)節(jié)點,然后網(wǎng)關(guān)節(jié)點經(jīng)過 Pull 機(jī)制來換取音訊,然后 Push 給終端設(shè)備。Pull+Offset 機(jī)制可以確保音訊的牢靠性,這個是 RocketMQ 的傳統(tǒng)模型,終端節(jié)點被迫承受網(wǎng)關(guān)節(jié)點的 Push,處理了終端設(shè)備輕量問題,實時性方面由于新音訊事情告訴機(jī)制而得到確保。
上圖中還有一個 Cache 模塊用于做音訊行列 cache,由于在大播送比場景下假如為每個終端設(shè)備都去發(fā)起行列 Pull 懇求則對 broker 讀壓力較大,既然每個懇求都去讀取相同的 Topic 行列,則可以復(fù)用本地行列 cache。
- lookup組件
上面的推拉模型經(jīng)過新音訊事情告訴機(jī)制來處理實時觸達(dá)問題,事情推送至網(wǎng)關(guān)的時分需求一個匹配查找進(jìn)程,雖然簡略的事情播送機(jī)制可以抵達(dá)必定的功能要求,但畢竟是一個播送模型,在大規(guī)模網(wǎng)關(guān)節(jié)點接入場景下仍然有功能瓶頸。別的,終端設(shè)備場景有許多狀況查詢訴求,如查找在線狀況,連接互踢等等,仍然需求一個 KV 查找組件,即 lookup。
咱們當(dāng)然可以運用外部 KV 存儲如 Redis,但咱們不能假定體系(產(chǎn)品)在用戶的交給環(huán)境,尤其是專有云的特別環(huán)境必定有牢靠的外部存儲服務(wù)依靠。
這個 lookup 查詢組件,實際上便是一個 KV 查詢,可以理解為是一個分布式內(nèi)存 KV,但要比分布式 KV 完成難度至少低一個等級。咱們回想一下一個分布式 KV 的根本要素有哪些:
如上圖所示,一般一個分布式 KV 讀寫流程是,Key 經(jīng)過 hash 得到一個邏輯 slot,slot 經(jīng)過一個映射表得到詳細(xì)的 node。Hash 算法一般是固定模數(shù),映射表一般是會集式裝備或運用一致性協(xié)議來裝備。節(jié)點擴(kuò)縮一般經(jīng)過調(diào)整映射表來完成。
分布式 KV 完成一般有三個根本要害點:
1)映射表一致性
讀寫都需求依據(jù)上圖的映射表進(jìn)行查找節(jié)點的,假如規(guī)矩不一致數(shù)據(jù)就亂了。映射規(guī)矩裝備自身可以經(jīng)過會集存儲,或者 zk、raft 這類協(xié)議確保強(qiáng)一致性,可是新舊裝備的切換不能確保節(jié)點一起進(jìn)行,仍然存在不一致性窗口。
2)多副本
經(jīng)過一致性協(xié)議同步存儲多個備份節(jié)點,用于容災(zāi)或多讀。
3)負(fù)載分配
slot 映射 node 便是一個分配,要確保 node 負(fù)載均衡,比方擴(kuò)縮狀況或許要進(jìn)行 slot 數(shù)據(jù)搬遷等。
咱們首要查詢和保存的是在線狀況數(shù)據(jù),假如存儲的 node 節(jié)點宕機(jī)丟掉數(shù)據(jù),咱們可以即時重建數(shù)據(jù),由于都是在線的,所以不需求考慮多副本問題,也不需求考慮擴(kuò)縮狀況 slot 數(shù)據(jù)搬遷問題,由于可以直接丟掉重建,只需求確保要害的一點:映射表的一致性,并且咱們有一個兜底機(jī)制——播送,當(dāng)分片數(shù)據(jù)不牢靠或不可用時退化到播送機(jī)制。
架構(gòu)規(guī)劃
依據(jù)前面的理論和模型剖析介紹,咱們在考慮用什么架構(gòu)形態(tài)來支撐一體化的方針,咱們從分層、擴(kuò)展、交給等方面進(jìn)行一下描繪。
- 分層架構(gòu)
咱們的方針是希望依據(jù) RocketMQ 完成一體化且自閉環(huán),但不希望 Broker 被侵入更多場景邏輯,咱們籠統(tǒng)了一個協(xié)議核算層,這個核算層可以是一個網(wǎng)關(guān),也可以是一個 broker 插件。Broker 專心處理 Queue 的事情以及為了滿足上面的核算需求做一些 Queue 存儲的適配或改造。協(xié)議核算層擔(dān)任協(xié)議接入,并且要可插拔布置。
- 擴(kuò)展規(guī)劃
咱們都知道音訊產(chǎn)品歸于 PaaS 產(chǎn)品,與上層 SaaS 事務(wù)貼得最近,為了習(xí)慣事務(wù)的不同需求,咱們大致整理一下要害的中心鏈路,在上下行鏈路上添加一些擴(kuò)展點,如鑒權(quán)邏輯這個最偏事務(wù)化的邏輯,不同的事務(wù)需求都不相同,又比方 Bridge 擴(kuò)展,其可以把終端設(shè)備狀況和音訊數(shù)據(jù)與一些外部生態(tài)體系(產(chǎn)品)打通。
- 交給規(guī)劃
好的架構(gòu)規(guī)劃仍是要考慮終究的落地問題,即怎樣交給?,F(xiàn)在面臨的現(xiàn)狀是公共云、專有云,乃至是開源等各種環(huán)境條件的落地,應(yīng)戰(zhàn)非常大。其間最大的應(yīng)戰(zhàn)是外部依靠問題,假如產(chǎn)品要強(qiáng)依靠一個外部體系或產(chǎn)品,那對整個交給就會有非常大的不確定性。
為了應(yīng)對各種雜亂的交給場景,一方面會規(guī)劃好擴(kuò)展接口,依據(jù)交給環(huán)境條件進(jìn)行適配完成;另一方面,咱們也會盡或許對一些模塊提供默許內(nèi)部完成,如上文提到的 lookup 組件,重復(fù)造輪子也是不得已而為之,這個或許便是做產(chǎn)品與做平臺的最大差異。
一致存儲內(nèi)核
前面臨整個協(xié)議模型和架構(gòu)進(jìn)行了詳細(xì)介紹,在 Broker 存儲層這塊還需求進(jìn)一步的改造和適配。咱們希望依據(jù) RocketMQ 一致存儲內(nèi)核來支撐終端和服務(wù)端的音訊收發(fā),完成一體化的方針。
前面也提到了終端音訊場景和服務(wù)端一個很大的差異是,終端有必要要有個客戶端維度的行列才能確保牢靠觸達(dá),而服務(wù)端可以運用會集式行列,由于音訊隨意哪臺機(jī)器消費都可以,可是終端音訊有必要清晰牢靠推送給詳細(xì)客戶端??蛻舳司S度的行列意味著數(shù)量級上比傳統(tǒng)的 RocketMQ 服務(wù)端 Topic 行列要大得多。
別的前面介紹的行列模型里邊,音訊也是依照 Topic 行列進(jìn)行存儲的,MQTT 的 Topic 是一個靈敏的多級 Topic,客戶端可以恣意生成,而不像服務(wù)端場景 Topic 是一個很重的元數(shù)據(jù)強(qiáng)辦理,這個也意味著 Topic 行列的數(shù)量級很大。
海量行列
咱們都知道像 Kafka 這樣的音訊行列每個 Topic 是獨立文件,可是跟著 Topic 增多音訊文件數(shù)量也增多,次序?qū)懢屯嘶闪穗S機(jī)寫,功能下降明顯。RocketMQ 在 Kafka 的根底上進(jìn)行了改進(jìn),運用了一個 Commitlog 文件來保存一切的音訊內(nèi)容,再運用 CQ 索引文件來表明每個 Topic 里邊的音訊行列,由于 CQ 索引數(shù)據(jù)較小,文件增多對 IO 影響要小許多,所以在行列數(shù)量上可以抵達(dá)十萬級??墒沁@終端設(shè)備行列場景下,十萬級的行列數(shù)量仍是太小了,咱們希望進(jìn)一步提升一個數(shù)量級,抵達(dá)百萬級行列數(shù)量,咱們引入了 Rocksdb 引擎來進(jìn)行 CQ 索引分發(fā)。
Rocksdb 是一個廣泛運用的單機(jī) KV 存儲引擎,具有高功能的次序?qū)懩芰ΑS捎谠蹅冇辛?commitlog 已具有了音訊次序流存儲,所以可以去掉 Rocksdb 引擎里邊的 WAL,依據(jù) Rocksdb 來保存 CQ 索引。在分發(fā)的時分咱們運用了 Rocksdb 的 WriteBatch 原子特性,分發(fā)的時分把當(dāng)時的 MaxPhyOffset 注入進(jìn)去,由于 Rocksdb 可以確保原子存儲,后續(xù)可以依據(jù)這個 MaxPhyOffset 來做 Recover 的 checkpoint。咱們提供了一個 Compaction 的自界說完成,來進(jìn)行 PhyOffset 的承認(rèn),以整理已刪去的臟數(shù)據(jù)。
輕量Topic
咱們都知道 RocketMQ 中的 Topic 是一個重要的元數(shù)據(jù),運用前要提前創(chuàng)立,并且會注冊到 namesrv 上,然后經(jīng)過 Topicroute 進(jìn)行服務(wù)發(fā)現(xiàn)。前面說了,終端場景訂閱的 Topic 比較靈敏可以恣意生成,假如依據(jù)現(xiàn)有的 RocketMQ 的 Topic 重辦理邏輯明顯有些困難。咱們界說了一種輕量的 Topic,專門支撐終端這種場景,不需求注冊 namesrv 進(jìn)行辦理,由上層協(xié)議邏輯層進(jìn)行自辦理,broker 只擔(dān)任存儲。
總結(jié)
本文首先介紹了端云音訊場景一體化的布景,然后重點剖析了終端音訊場景特色,以及終端音訊場景支撐模型,最后對架構(gòu)和存儲內(nèi)核進(jìn)行了論述。咱們希望依據(jù) RocketMQ 一致內(nèi)核一體化支撐終端和服務(wù)端不同場景的音訊接入方針,以可以給運用者帶來一體化的價值,如下降存儲本錢,避免數(shù)據(jù)在不同體系間同步帶來的一致性應(yīng)戰(zhàn)。