做者 | 凌楚 阿里巴巴開發工程師git
導讀:自 19 年末開始,支持 Apache RocketMQ 的 Network Filter 歷時 4 個月的 Code Review( Pull Request),於本月正式合入 CNCF Envoy 官方社區( RocketMQ Proxy Filter 官方文檔),這使得 RocketMQ 成爲繼 Dubbo 以後,國內第二個成功進入 Service Mesh 官方社區的中間件產品。
主要流程以下圖:github
圖 1數據庫
簡述一下 Service Mesh 下 RocketMQ 消息的發送與消費過程:編程
Service Mesh 經常被稱爲下一代微服務,這一方面揭示了在早期 Mesh 化浪潮中微服務是絕對的主力軍,另外一方面,微服務的 Mesh 化也相對更加便利,而隨着消息隊列和一些數據庫產品也逐漸走向 Service Mesh,各個產品在這個過程當中也會有各自的問題亟需解決,RocketMQ 也沒有例外。網絡
RocketMQ 的網絡模型比 RPC 更加複雜,是一套有狀態的網絡交互,這主要體如今兩點:併發
對於前者,使得現有的 SDK 徹底沒法使用分區順序消息,由於發送請求和消費請求 RPC 的內容中並不包含 IP/(BrokerName + BrokerId) 等信息,致使使用了 Mesh 以後的 SDK 不能保證發送和消費的 Queue 在同一臺 Broker 上,即 Broker 信息自己在 Mesh 化的過程當中被抹除了。固然這一點,對於只有一臺 Broker 的全局順序消息而言是不存在的,由於數據平面在負載均衡的時候並無其餘 Broker 的選擇,所以在路由層面上,全局順序消息是不存在問題的。app
對於後者,RocketMQ 的 Pull/Push Consumer 中 Queue 是負載均衡的基本單位,原生的 Consumer 中實際上是要感知與本身處於同一 ConsumerGroup 下消費同一 Topic 的 Consumer 數目的,每一個 Consumer 根據本身的位置來選擇相應的 Queue 來進行消費,這些 Queue 在一個 Topic-ConsumerGroup 映射下是被每一個 Consumer 獨佔的,而這一點在現有的數據平面是很難實現的,並且,現有數據平面的負載均衡無法作到 Queue 粒度,這使得 RocketMQ 中的負載均衡策略已經再也不適用於 Service Mesh 體系下。負載均衡
此時咱們將目光投向了 RocketMQ 爲支持 HTTP 而開發的 Pop 消費接口,在 Pop 接口下,每一個 Queue 能夠再也不是被當前 Topic-ConsumerGroup 的 Consumer 獨佔的,不一樣的消費者能夠同時消費一個 Queue 裏的數據,這爲咱們使用 Envoy 中原生的負載均衡策略提供了可能。less
圖 2編程語言
圖 2 右側即爲 Service Mesh 中 Pop Consumer 的消費狀況,在 Envoy 中咱們會忽略掉 SDK 傳來的 Queue 信息。
在集團內部,Nameserver 中保存着上 GB 的 Topic 路由信息,在 Mesh 中,咱們將這部分抽象成 CDS,這使得對於沒法預先知道應用所使用的 Topic 的情形而言,控制平面只能全量推送 CDS,這無疑會給控制平面帶來巨大的穩定性壓力。
在 Envoy 更早期,是徹底的全量推送,在數據平面剛啓動時,控制平面會下發全量的 xDS 信息,以後控制平面則能夠主動控制數據的下發頻率,可是無疑下發的數據依舊是全量的。後續 Envoy 支持了部分的 delta xDS API,便可如下發增量的 xDS 數據給數據平面,這固然使得對於已有的 sidecar,新下發的數據量大大下降,可是 sidecar 中擁有的 xDS 數據依然是全量的,對應到 RocketMQ ,即全量的 CDS 信息都放在內存中,這是咱們不可接受的。因而咱們但願可以有 on-demand CDS 的方式使得 sidecar 能夠僅僅獲取本身想要的 CDS 。而此時正好 Envoy 支持了 delta CDS,並僅支持了這一種 delta xDS。其實此時擁有 delta CDS 的 xDS 協議自己已經提供了 on-demand CDS 的能力,可是不管是控制平面仍是數據平面並無暴露這種能力,因而在這裏對 Envoy 進行了修改並暴露了相關接口使得數據平面能夠主動向控制平面發起對指定 CDS 的請求,並基於 delta gRPC 的方式實現了一個簡單的控制平面。Envoy 會主動發起對指定 CDS 資源的請求,並提供了相應的回調接口供資源返回時進行調用。
對於 on-demand CDS 的敘述對應到 RocketMQ 的流程中是這樣的,當 GetTopicRoute
或者 SendMessage
的請求到達 Envoy 時,Envoy 會 hang 住這個流程併發起向控制平面中相應 CDS 資源的請求並直到資源返回後重啓這個流程。
關於 on-demand CDS 的修改,以前還向社區發起了 Pull Request ,如今看來當時的想法仍是太不成熟了。緣由是咱們這樣的作法徹底忽略了 RDS 的存在,而將 CDS 和 Topic 實現了強綁定,甚至名稱也如出一轍,關於這一點,社區的 Senior Maintainer [@htuch ]() 對咱們的想法進行了反駁,大意就是實際上的 CDS 資源名可能帶上了負載均衡方式,inbound/outbound 等各類 prefix 和 suffix,不能直接等同於 Topic 名,更重要的是社區賦予 CDS 自己的定義是脫離於業務的,而咱們這樣的作法過於 tricky ,是與社區的初衷背道而馳的。
所以咱們就須要加上 RDS 來進行抽象,RDS 經過 topic 和其餘信息來定位到具體所須要的 CDS 名,因爲做爲數據平面,沒法預先在代碼層面就知道所須要找的 CDS 名,那麼如此一來,經過 CDS 名來作 on-demand CDS 就更無從談起了,所以從這一點出發只能接受全量方案,不過好在這並不會影響代碼貢獻給社區。
route_config: name: default_route routes: - match: topic: exact: mesh headers: - name: code exact_match: 105 route: cluster: foo-v145-acme-tau-beta-lambda
上面能夠看到對於 topic 名爲 mesh 的請求會被 RDS 路由到 foo-v145-acme-tau-beta-lambda 這個 CDS 上,事先咱們只知道 topic 名,沒法知道被匹配到的 CDS 資源名。
現在站在更高的視角,發現這個錯誤很簡單,可是其實這個問題咱們直到後續 code review 時才及時糾正,確實能夠更早就作得更好。
不過從目前社區的動態來看,on-demand xDS 或許已是一個 roadmap,起碼目前 xDS 已經全系支持 delta ,VHDS 更是首度支持了 on-demand 的特性。
A service mesh is a dedicated infrastructure layer for handling service-to-service communication. It’s responsible for the reliable delivery of requests through the complex topology of services that comprise a modern, cloud native application. In practice, the service mesh is typically implemented as an array of lightweight network proxies that are deployed alongside application code, without the application needing to be aware.
這是 Service Mesh 這個詞的創造者 William Morgan 對其作出的定義,歸納一下:做爲網絡代理,並對用戶透明,承擔做爲基礎設施的職責。
圖 3
這裏的職責在 RocketMQ 中包括服務發現、負載均衡、流量監控等職責,使得調用方和被代理方的職責大大下降了。
固然目前的 RocketMQ Filter 爲了保證兼容性作出了不少讓步,好比爲了保證 SDK 能夠成功獲取到路由,將路由信息聚合包裝成了 TopicRouteData
返回給了 SDK ,可是在理想狀況下,SDK 自己已經不須要關心路由了,純爲 Mesh 情景設計的 SDK 是更加精簡的,再也不會有消費側 Rebalance,發送和消費的服務發現,甚至在將來像消息體壓縮和 schema 校驗這些功能 SDK 和 Broker 或許均可以不用再關心,來了就發送/消費,發送/消費完就走或許纔是 RocketMQ Mesh 的終極形態。
圖 4
目前 RocketMQ Filter 具有了普通消息的發送和 Pop 消費能力,可是若是想要具有更加完整的產品形態,功能上還有一些須要補充:
起初,RocketMQ Filter 的初次 Pull Request 就包含了當前幾乎所有的功能,致使了一個超過 8K 行的超大 PR,感謝@天千 在 Code Review 中所作的工做,很是專業,幫助了咱們更快地合入社區。
另外,Envoy 社區的 CI 實在太嚴格了,嚴格要求 97% 以上的單測行覆蓋率,Bazel 源碼級依賴,純靜態連接,自己無 cache 編譯 24 邏輯核心 CPU 和 load 均打滿至少半個小時才能編完,社區的各類 CI 跑完一次則少說兩三個小時,多則六七個小時,並對新提交的代碼有着極其嚴苛的語法和 format 要求,這使得在 PR 中修改一小部分代碼就可能帶來大量的單測變更和 format 需求,不過好的是單測能夠很方便地幫助咱們發現一些內存 case 。客觀上來講,官方社區以這麼高的標準來要求 contributors 確實能夠很大程度上控制住代碼質量,咱們在補全單測的過程當中,仍是發現並解決了很多自身的問題,總得來講仍是有必定必要的,畢竟對於 C++ 代碼而言,一旦生產環境出問題,調試和追蹤起來會困可貴多。
最後,RocketMQ Filter 的代碼由我和@叔田 共同完成,對於一個沒什麼開源經驗的我來講,爲這樣的熱門社區貢獻代碼是一次很是寶貴的經歷,同時也感謝叔田在此過程當中給予的幫助和建議。
這裏有足夠多的業務場景、足夠大的消息生態、足夠深的分佈式技術等着你們前來探索,若是你知足:
歡迎加入並參與新一代雲原生中間件建設!簡歷提交地址:yangkun.ayk@alibaba-inc.com。
爲了更多開發者可以享受到 Serverless 帶來的紅利,這一次,咱們集結了 10+ 位阿里巴巴 Serverless 領域技術專家,打造出最適合開發者入門的 Serverless 公開課,讓你即學即用,輕鬆擁抱雲計算的新範式——Serverless。
點擊便可免費觀看課程:https://developer.aliyun.com/learning/roadmap/serverless
「 阿里巴巴雲原生關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,作最懂雲原生開發者的公衆號。」