RocketMQ 5.0:无状态代理模式的探索与实践
本文作者:金吉祥, Apache RocketMQ PMC Member,阿里云智能高級技術專家
背景
首先,讓我們來看下是遇到了哪些痛點問題,促使我們去探索一種無狀態代理的RocketMQ新架構的;
RocketMQ 擁有一套極簡的架構,多語言客戶端通過自定義的 Remoting 協議與后端 NameServer 和 Broker建立 TCP 長連接,然后進行消息的路由發現以及完整的消息收發。這套架構的優勢是:架構極簡,客戶端 與 Broker 通過 TCP 直連的模式,擁有較高的性能以及較低的延遲。同時,這套架構采取的是隊列模型,非常適合基于隊列批量高速拉取消息的場景。
同時,RocketMQ 在上云過程中面臨了各種各樣的挑戰。
首先,云上用戶需要更為豐富的業務語義消息,包括事務、定時、順序以及死信等。為了滿足用戶的業務側需求,需要在原先架構的客戶端側和 Broker側分別進行開發,用戶必須升級客戶端之后才能享受新功能。
其次,上云過程需要面對更為復雜的網絡環境,不同場景下需要不同類型網絡的接入。有些用戶為了便捷性,期望能夠交付公網的接入點,讓不同地域的消費者、發送者都能連接到同一個消息服務;而另一種用戶為了安全性,需要內網接入點來隔離一些非法的網絡請求;RocketMQ原先的架構在應對多網絡類型的接入訴求時,成本是比較高的,多網絡類型的接入必須同時覆蓋NameServer和Broker的每一臺機器才行。比如我們需要對內部 Broker進行擴容場景下,如果原先的 Broker 擁有多種類型的網絡接入訴求,那么新擴容的 Broker也需要額外綁定上多種類型的網絡接入點之后才能正常對外交付。
做下總結,面對上云的挑戰,此前的架構逐漸暴露出了如下諸多痛點:
① 富客戶端形態:客戶端包含了大量企業級特性。用戶必須升級客戶端才能享受新功能,過程十分漫長。且同樣的功能交付必須在多個多語言版本里都進行適配才能滿足多語言客戶端的接入,工作量巨大。
② 客戶端與Broker所有節點的直連模式滿足多類型網絡接入的成本較高。
③ 按照隊列進行負載均衡和消息拉取,后端擴縮容時會觸發客戶端rebalance,導致消息延遲或重復消費,消費者會有明顯的感知;此外, 基于隊列的模型非常容易導致一個用戶飽受困擾的問題:單個故障消費者消費卡住會導致消息在服務端大量堆積。
RocketMQ5.0無狀態代理模式
為了解決上述痛點,RocketMQ 5.0 提出了無狀態代理模式。
新架構在原先的客戶端和Broker中間插入了代理層。策略上是將客戶端的無狀態功能盡可能下移到代理層,同時也將 Broker側的無狀態功能盡可能上移到代理層。在這里,我們將客戶端的原有的負載均衡機制、故障隔離、push/pop消費模型下移到了代理層,將 Broker 的訪問控制、多協議適配、客戶端治理以及NameServer 的消息路由能力上移到了代理層,最終打造出的代理層的豐富能力,包含訪問控制、多協議適配、通用業務能力、治理能力以及可觀測性等。
在構建代理層的過程中,我們必須堅持的一個原則就是:客戶端和 Broker 往代理層遷移的能力必須是無狀態的,這樣才能保證后續代理層是可以隨著承接的流量大小進行動態擴縮容的。
在引入無狀態代理層后,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無狀態代理模式技術詳解
新架構在客戶端和 Broker 之間引入了代理層,客戶端所有流量都需要多一跳網絡、多經歷一次序列化/反序列化的過程,這對端到端消息延遲敏感的用戶是極其不友好的,因此我們設計了合并部署形態。
合并部署形態下,Proxy和 Broker 按照1:1的方式對等部署,且 Proxy和 Broker 實現進程內通信,滿足低延遲、高吞吐的訴求。同時,Proxy仍然具備多協議適配的能力,客戶端會與所有 Proxy建立連接進行消息收發保證能夠消費到所有消息。
代碼實現上,我們通過構造器來實現合并部署和分離部署的兩種形態。用戶可自行選擇部署形態。如果用戶選擇合并部署的形態,則在構建 Proxy處理器之前,會先構造 BrokerController,并向 Proxy的處理器注冊,本質上是為了告知Proxy處理器:后續的請求往我這個BrokeController 發;如果用戶選擇分離部署模式,則無須構建BrokerController,直接啟動Proxy處理器即可。
對于這兩種部署模式的比較,首先,合并部署和分離部署同時具備了多協議的適配能力,都能夠解析用戶側和客戶端側的多協議請求;且具備模型抽象,能夠解決富客戶端帶來的一系列痛點。
部署架構上,合并部署是一體化的架構,易于運維;分離部署是分層的架構,Proxy組件獨立部署,Proxy和 broker按業務水位分別進行擴縮。
性能上,合并部署架構少一跳網絡和一次序列化,有較低的延遲和較高的吞吐;分離部署多一跳網絡和一次序列化,開銷和延遲有所增加。延遲具體增加多少主要依賴于 Proxy和 Broker 之間的網絡延遲。
資源調度上,合并部署狀態下比較容易獲得穩定的成本模型,因為組件單一;分離部署形態下Proxy是 CPU 密集型,Broker和NameServer 也逐漸退化成存儲和 IO 密集型,需要分配比較多的內存和磁盤空間,因此需要進行細粒度的分配和資源規劃,才能讓分離部署形態資源的利用率達到最大化。
多網絡類型接入成本上,合并部署成本較高,客戶端需要與每一個 Proxy副本都嘗試建立連接,然后進行消息收發。因此,多網絡接入類型場景下,Proxy進行擴縮容時需要為每臺Proxy綁定不同類型的網絡;分離部署模式成本較低,僅需要在獨立部署的 Proxy層嘗試綁定多網絡類型的接入點即可,同時是多臺Proxy綁定到同一個類型的網絡接入點即可。
針對業務上的選型建議:
如果對端到端的延遲比較敏感,或期望使用較少的人力去運維很大集群規模的RocketMQ部署,或只需要在單一的網絡環境中使用RocketMQ的,比如只需內網訪問,則建議采用合并部署的模式。
如果有多網絡類型接入的訴求比如同時需要內網和公網的訪問能力或想要對RocketMQ進行個性化定制,則建議采用分離部署的模式,可以將多網絡類型的接入點封閉在 Proxy層,將個性化定制的改造點也封閉在Proxy層。
社區新推出的PopConsumer消費模式和原先的PushConsumer 消費模式存在較大區別。PushConsumer 是基于隊列模型的消費模式,但存在一些遺留問題。比如單個 PushConsumer 消費卡住會導致服務端側消息堆積。新推出的Pop消費模式是基于消息的消費模型,PopConsumer 會嘗試和所有 broker 連接并消費消息,即便有一個PopConsumer消費卡住,其他正常的PopConsumer依然能從所有 broker里拉取到消息進行消費,不會出現消息堆積。
從Proxy代理層角度看,它能夠無縫適配 PushConsumer 和 PopConsumer,最終將兩種消費模式都映射到 Pop 消費模式,與后端 Broker 建立消費連接。具體來看,PushConsumer 里的pull請求會被轉成PopConsumer 消費模式里的 pop 請求,提交位點的 UpdateConsumeOffset 請求會被轉換成消息級別的 ACK 請求,SendMessageBack會被轉換成修改消息的不可見時間的請求,以達到重新被消費的目的。
Proxy最上層為協議的適配層,通過不同的端口對客戶端暴露服務,不同協議通過不同端口請求到Proxy之后,協議適配層會進行適配,通過通用的 MessagingProcessor 模塊,將send、pop、ack、ChangeInvisibleTime等請求轉換成后端 Remoting 協議的請求,與后端的 Broker 和NameServer建立連接,進行消息收發。
多協適配的優勢有以下幾個方面:
① 加速了RocketMQ的云原生化,比如更容易與Service Mesh相結合。
② 基于 gRPC 的標準性、兼容性及多協議多語言傳輸層代碼的生成能力,打造RocketMQ的多語言瘦客戶端,能夠充分利用gRPC插件化的連接多路復用、序列化/反序列化的能力,讓RocketMQ客戶端更加輕量化,將更多精力聚焦在消息領域的業務邏輯。
做下技術方案的總結:
無狀態代理模式通過客戶端下移、Broker側上移無狀態的能力到代理層,將訪問控制、客戶端治理、流量治理等業務收斂到代理層,既能夠滿足快速迭代的訴求,又能對變更進行收斂,更好保障整個架構的穩定性:有了分層架構之后,更多業務邏輯的開發會聚焦在 Proxy層,下一層的 Broker和 NameServer 趨于穩定,可以更多地關注存儲的特性。Proxy的發布頻率遠遠高于底層 Broker 的發布頻率,因此問題收斂之后,穩定性也得到了保證。
多協議適配,基于gRPC的標準性、兼容性以及多語言傳輸層代碼生成的能力打造RocketMQ的多語言瘦客戶端。
Push 到 Pop 消費模式的無感知切換,將消費位點的維護收斂到 Broker, 解決了單一消費者卡住導致消息堆積的歷史遺留問題。
另外,我們也嘗試探索了可分可合的部署形態,保證同一套代碼可分可合,滿足不同場景下對性能部署成本、易運維性的差異化訴求。在大部分的場景下,依然建議選擇合并部署的形態。如果有對多網絡類型接入的訴求,或對RocketMQ 有極強的定制化訴求,則建議選擇分離部署的形態,以達到更好的可擴展性。
代理層無狀態的特性,極大降低了適配多類型網絡接入訴求的成本。
未來規劃
未來,我們期望RocketMQ底層的Broker和NameServer 更多聚焦在存儲的特性上,比如業務型消息存儲的事務、定時、順序等,快速構建消息索引、打造一致性多副本提升消息可靠性、多級存儲來達到更大的存儲空間等。
其次,對無狀態代理層依照可插拔的特性開發,比如對訪問控制、抽象模型、協議適配、通用業務能力、治理能力等按模塊進行劃分,使語義更豐富,可按照不同場景的訴求可插拔地部署各種組件。
最后,我們期望這一套架構能夠支持阿里云上的多產品形態,致力于打造云原生消息非常豐富的產品矩陣。
加入 Apache RocketMQ 社區
十年鑄劍,Apache RocketMQ 的成長離不開全球接近 500 位開發者的積極參與貢獻,相信在下個版本你就是 Apache RocketMQ 的貢獻者,在社區不僅可以結識社區大牛,提升技術水平,也可以提升個人影響力,促進自身成長。
社區 5.0 版本正在進行著如火如荼的開發,另外還有接近 30 個 SIG(興趣小組)等你加入,歡迎立志打造世界級分布式系統的同學加入社區,添加社區開發者微信:rocketmq666 即可進群,參與貢獻,打造下一代消息、事件、流融合處理平臺。
總結
以上是生活随笔為你收集整理的RocketMQ 5.0:无状态代理模式的探索与实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redhat Linux红帽安装Java
- 下一篇: VOC格式数据分析统计和处理