RocketMQ 5.0:無狀態代理模式的探索與實踐

語言: CN / TW / HK

1.jpeg

本文作者:金吉祥, Apache RocketMQ PMC Member,阿里雲智慧高階技術專家

背景

2.jpeg

首先,讓我們來看下是遇到了哪些痛點問題,促使我們去探索一種無狀態代理的RocketMQ新架構的;

RocketMQ 擁有一套極簡的架構,多語言客戶端通過自定義的 Remoting 協議與後端 NameServer 和 Broker建立 TCP 長連線,然後進行訊息的路由發現以及完整的訊息收發。這套架構的優勢是:架構極簡,客戶端 與 Broker 通過 TCP 直連的模式,擁有較高的效能以及較低的延遲。同時,這套架構採取的是佇列模型,非常適合基於佇列批量高速拉取訊息的場景。

同時,RocketMQ 在上雲過程中面臨了各種各樣的挑戰。

首先,雲上使用者需要更為豐富的業務語義訊息,包括事務、定時、順序以及死信等。為了滿足使用者的業務側需求,需要在原先架構的客戶端側和 Broker側分別進行開發,使用者必須升級客戶端之後才能享受新功能。

其次,上雲過程需要面對更為複雜的網路環境,不同場景下需要不同型別網路的接入。有些使用者為了便捷性,期望能夠交付公網的接入點,讓不同地域的消費者、傳送者都能連線到同一個訊息服務;而另一種使用者為了安全性,需要內網接入點來隔離一些非法的網路請求;RocketMQ原先的架構在應對多網路型別的接入訴求時,成本是比較高的,多網路型別的接入必須同時覆蓋NameServer和Broker的每一臺機器才行。比如我們需要對內部 Broker進行擴容場景下,如果原先的 Broker 擁有多種型別的網路接入訴求,那麼新擴容的 Broker也需要額外繫結上多種型別的網路接入點之後才能正常對外交付。

做下總結,面對上雲的挑戰,此前的架構逐漸暴露出瞭如下諸多痛點:

① 富客戶端形態:客戶端包含了大量企業級特性。使用者必須升級客戶端才能享受新功能,過程十分漫長。且同樣的功能交付必須在多個多語言版本里都進行適配才能滿足多語言客戶端的接入,工作量巨大。

② 客戶端與Broker所有節點的直連模式滿足多型別網路接入的成本較高。

③ 按照佇列進行負載均衡和訊息拉取,後端擴縮容時會觸發客戶端rebalance,導致訊息延遲或重複消費,消費者會有明顯的感知;此外, 基於佇列的模型非常容易導致一個使用者飽受困擾的問題:單個故障消費者消費卡住會導致訊息在服務端大量堆積。

RocketMQ5.0無狀態代理模式

3.png

為了解決上述痛點,RocketMQ 5.0 提出了無狀態代理模式。

新架構在原先的客戶端和Broker中間插入了代理層。策略上是將客戶端的無狀態功能儘可能下移到代理層,同時也將 Broker側的無狀態功能儘可能上移到代理層。在這裡,我們將客戶端的原有的負載均衡機制、故障隔離、push/pop消費模型下移到了代理層,將 Broker 的訪問控制、多協議適配、客戶端治理以及NameServer 的訊息路由能力上移到了代理層,最終打造出的代理層的豐富能力,包含訪問控制、多協議適配、通用業務能力、治理能力以及可觀測性等。

在構建代理層的過程中,我們必須堅持的一個原則就是:客戶端和 Broker 往代理層遷移的能力必須是無狀態的,這樣才能保證後續代理層是可以隨著承接的流量大小進行動態擴縮容的。

4.png

在引入無狀態代理層後,RocketMQ原先客戶端、Broker的直連架構就演變為上圖的無狀態代理模式架構:

從流量上看,代理層承接了客戶端側所有流量, Broker 和 NameServer 不再直接對使用者暴露,使用者唯一能看到的元件只有代理層(Proxy)。

這裡,Proxy的職責包括以下幾個方面:

  • 多協議適配: Proxy具備解析和適配不同協議的能力,包含 remoting 、gRPC、HTTP 以及後續可能衍生出來的MQTT和AMQP等協議。Proxy對不同協議進行適配、解析,最終統一翻譯成與後端 Broker 和 NameServer 之間的 remoting協議。

  • 流量治理和流量分發: Proxy承接了客戶端側所有流量,因此能夠很輕鬆地基於某些規則識別出當前流量的特性,然後根據一定的規則將流量分發到後端不同的 Broker叢集,甚至進行精準的流量控制、限流等。

  • 功能擴充套件:包括訪問控制比如允許哪個使用者訪問後端Broker叢集中的哪個 Topic、訊息軌跡的統一收集以及整體的可觀測性等。

  • Proxy能扮演NameServer,交付給客戶端查詢TopicRoute的能力。

  • Proxy能夠無縫相容使用者側的Pop或者Push消費模式:在Proxy和Broker側採用Pop消費模式來避免單個佇列被鎖導致訊息在服務端堆積的歷史遺留問題。

同時,我們也可以看到Proxy具有以下兩大特性:

① 無狀態,可根據使用者以及客戶端的流量進行水平擴縮容。

② 計算型,比較消耗CPU,因此在部署時需要儘可能給Proxy分配多些CPU。

做下總結,無狀態代理模式解決了原先架構的多個痛點:

① 將客戶端大量業務邏輯下移到代理層,打造出輕量客戶端。同時,依託於 gRPC協議的標準化以及傳輸層程式碼自動生成的能力,能夠快速適配多種語言的客戶端。

② 客戶端只會與Proxy層連線,針對多網路型別接入的訴求,可以將多網路型別繫結到Proxy層,由於Broker 和 NameServer不再直接對客戶端暴露,轉而只需對 Proxy暴露內網的連線即可,多網路型別接入的訴求可以只在Proxy這個元件就封閉掉;同時,Proxy的無狀態的特性保證了多型別網路接入是與叢集規模無關的。

③ 消費模式進行了無感知切換,無論客戶端側選擇的是Pop還是Push消費模式,最終統一替換為Proxy與Broker側的Pop消費模式,避免單個客戶端消費卡住導致服務端訊息堆積的歷史問題。

RocketMQ無狀態代理模式技術詳解

5.png

新架構在客戶端和 Broker 之間引入了代理層,客戶端所有流量都需要多一跳網路、多經歷一次序列化/反序列化的過程,這對端到端訊息延遲敏感的使用者是極其不友好的,因此我們設計了合併部署形態。

合併部署形態下,Proxy和 Broker 按照1:1的方式對等部署,且 Proxy和 Broker 實現程序內通訊,滿足低延遲、高吞吐的訴求。同時,Proxy仍然具備多協議適配的能力,客戶端會與所有 Proxy建立連線進行訊息收發保證能夠消費到所有訊息。

程式碼實現上,我們通過構造器來實現合併部署和分離部署的兩種形態。使用者可自行選擇部署形態。如果使用者選擇合併部署的形態,則在構建 Proxy處理器之前,會先構造 BrokerController,並向 Proxy的處理器註冊,本質上是為了告知Proxy處理器:後續的請求往我這個BrokeController 發;如果使用者選擇分離部署模式,則無須構建BrokerController,直接啟動Proxy處理器即可。

6.png

對於這兩種部署模式的比較,首先,合併部署和分離部署同時具備了多協議的適配能力,都能夠解析使用者側和客戶端側的多協議請求;且具備模型抽象,能夠解決富客戶端帶來的一系列痛點。

部署架構上,合併部署是一體化的架構,易於運維;分離部署是分層的架構,Proxy元件獨立部署,Proxy和 broker按業務水位分別進行擴縮。

效能上,合併部署架構少一跳網路和一次序列化,有較低的延遲和較高的吞吐;分離部署多一跳網路和一次序列化,開銷和延遲有所增加。延遲具體增加多少主要依賴於 Proxy和 Broker 之間的網路延遲。

資源排程上,合併部署狀態下比較容易獲得穩定的成本模型,因為元件單一;分離部署形態下Proxy是 CPU 密集型,Broker和NameServer 也逐漸退化成儲存和 IO 密集型,需要分配比較多的記憶體和磁碟空間,因此需要進行細粒度的分配和資源規劃,才能讓分離部署形態資源的利用率達到最大化。

多網路型別接入成本上,合併部署成本較高,客戶端需要與每一個 Proxy副本都嘗試建立連線,然後進行訊息收發。因此,多網路接入型別場景下,Proxy進行擴縮容時需要為每臺Proxy繫結不同型別的網路;分離部署模式成本較低,僅需要在獨立部署的 Proxy層嘗試繫結多網路型別的接入點即可,同時是多臺Proxy繫結到同一個型別的網路接入點即可。

針對業務上的選型建議:

如果對端到端的延遲比較敏感,或期望使用較少的人力去運維很大叢集規模的RocketMQ部署,或只需要在單一的網路環境中使用RocketMQ的,比如只需內網訪問,則建議採用合併部署的模式。

如果有多網路型別接入的訴求比如同時需要內網和公網的訪問能力或想要對RocketMQ進行個性化定製,則建議採用分離部署的模式,可以將多網路型別的接入點封閉在 Proxy層,將個性化定製的改造點也封閉在Proxy層。

7.png

社群新推出的PopConsumer消費模式和原先的PushConsumer 消費模式存在較大區別。PushConsumer 是基於佇列模型的消費模式,但存在一些遺留問題。比如單個 PushConsumer 消費卡住會導致服務端側訊息堆積。新推出的Pop消費模式是基於訊息的消費模型,PopConsumer 會嘗試和所有 broker 連線並消費訊息,即便有一個PopConsumer消費卡住,其他正常的PopConsumer依然能從所有 broker里拉取到訊息進行消費,不會出現訊息堆積。

從Proxy代理層角度看,它能夠無縫適配 PushConsumer 和 PopConsumer,最終將兩種消費模式都對映到 Pop 消費模式,與後端 Broker 建立消費連線。具體來看,PushConsumer 裡的pull請求會被轉成PopConsumer 消費模式裡的 pop 請求,提交位點的 UpdateConsumeOffset 請求會被轉換成訊息級別的 ACK 請求,SendMessageBack會被轉換成修改訊息的不可見時間的請求,以達到重新被消費的目的。

8.png

Proxy最上層為協議的適配層,通過不同的埠對客戶端暴露服務,不同協議通過不同埠請求到Proxy之後,協議適配層會進行適配,通過通用的 MessagingProcessor 模組,將send、pop、ack、ChangeInvisibleTime等請求轉換成後端 Remoting 協議的請求,與後端的 Broker 和NameServer建立連線,進行訊息收發。

多協適配的優勢有以下幾個方面:

① 加速了RocketMQ的雲原生化,比如更容易與Service Mesh相結合。

② 基於 gRPC 的標準性、相容性及多協議多語言傳輸層程式碼的生成能力,打造RocketMQ的多語言瘦客戶端,能夠充分利用gRPC外掛化的連線多路複用、序列化/反序列化的能力,讓RocketMQ客戶端更加輕量化,將更多精力聚焦在訊息領域的業務邏輯。

9.png

做下技術方案的總結:

無狀態代理模式通過客戶端下移、Broker側上移無狀態的能力到代理層,將訪問控制、客戶端治理、流量治理等業務收斂到代理層,既能夠滿足快速迭代的訴求,又能對變更進行收斂,更好保障整個架構的穩定性:有了分層架構之後,更多業務邏輯的開發會聚焦在 Proxy層,下一層的 Broker和 NameServer 趨於穩定,可以更多地關注儲存的特性。Proxy的釋出頻率遠遠高於底層 Broker 的釋出頻率,因此問題收斂之後,穩定性也得到了保證。

多協議適配,基於gRPC的標準性、相容性以及多語言傳輸層程式碼生成的能力打造RocketMQ的多語言瘦客戶端。

Push 到 Pop 消費模式的無感知切換,將消費位點的維護收斂到 Broker, 解決了單一消費者卡住導致訊息堆積的歷史遺留問題。

另外,我們也嘗試探索了可分可合的部署形態,保證同一套程式碼可分可合,滿足不同場景下對效能部署成本、易運維性的差異化訴求。在大部分的場景下,依然建議選擇合併部署的形態。如果有對多網路型別接入的訴求,或對RocketMQ 有極強的定製化訴求,則建議選擇分離部署的形態,以達到更好的可擴充套件性。

代理層無狀態的特性,極大降低了適配多型別網路接入訴求的成本。

未來規劃

10.png

未來,我們期望RocketMQ底層的Broker和NameServer 更多聚焦在儲存的特性上,比如業務型訊息儲存的事務、定時、順序等,快速構建訊息索引、打造一致性多副本提升訊息可靠性、多級儲存來達到更大的儲存空間等。

其次,對無狀態代理層依照可插拔的特性開發,比如對訪問控制、抽象模型、協議適配、通用業務能力、治理能力等按模組進行劃分,使語義更豐富,可按照不同場景的訴求可插拔地部署各種元件。

最後,我們期望這一套架構能夠支援阿里雲上的多產品形態,致力於打造雲原生訊息非常豐富的產品矩陣。

加入 Apache RocketMQ 社群

十年鑄劍,Apache RocketMQ 的成長離不開全球接近 500 位開發者的積極參與貢獻,相信在下個版本你就是 Apache RocketMQ 的貢獻者,在社群不僅可以結識社群大牛,提升技術水平,也可以提升個人影響力,促進自身成長。

社群 5.0 版本正在進行著如火如荼的開發,另外還有接近 30 個 SIG(興趣小組)等你加入,歡迎立志打造世界級分散式系統的同學加入社群,新增社群開發者微信:rocketmq666 即可進群,參與貢獻,打造下一代訊息、事件、流融合處理平臺。

11.jpeg

微信掃碼新增小火箭進群

另外還可以加入釘釘群與 RocketMQ 愛好者一起廣泛討論:

12.png

釘釘掃碼加群

關注「Apache RocketMQ」公眾號,獲取更多技術乾貨