作者 | 凌楚 阿里巴巴開發工程師
導讀:自 19 年底開始,支持 Apache RocketMQ 的 Network Filter 歷時 4 個月的 Code Review(Pull Request),於本月正式合入 CNCF Envoy 官方社區(RocketMQ Proxy Filter 官方文檔),這使得 RocketMQ 成為繼 Dubbo 之後,國內第二個成功進入 Service Mesh 官方社區的中間件產品。
Service Mesh 下的消息收發
主要流程如下圖:
圖 1
簡述一下 Service Mesh 下 RocketMQ 消息的發送與消費過程:
- Pilot 獲取到 Topic 的路由信息並通過 xDS 的形式下發給數據平面/Envoy ,Envoy 會代理 SDK 向 Broker/Nameserver 發送的所有的網絡請求;
- 發送時,Envoy 通過 request code 判斷出請求為發送,並根據 topic 和 request code 選出對應的 CDS,然後通過 Envoy 提供的負載均衡策略選出對應的 Broker 併發送,這裡會使用數據平面的 subset 機制來確保選出的 Broker 是可寫的;
- 消費時,Envoy 通過 request code 判斷出請求為消費,並根據 topic 和 request code 選出對應的 CDS,然後和發送一樣選出對應的 Broker 進行消費(與發送類似,這裡也會使用 subset 來確保選出的 Broker 是可讀的),並記錄相應的元數據,當消息消費 SDK 發出 ACK 請求時會取出相應的元數據信息進行比對,再通過路由來準確將 ACK 請求發往上次消費時所使用的 Broker。
RocketMQ Mesh 化所遭遇的難題
Service Mesh 常常被稱為下一代微服務,這一方面揭示了在早期 Mesh 化浪潮中微服務是絕對的主力軍,另一方面,微服務的 Mesh 化也相對更加便利,而隨著消息隊列和一些數據庫產品也逐漸走向 Service Mesh,各個產品在這個過程中也會有各自的問題亟需解決,RocketMQ 也沒有例外。
有狀態的網絡模型
RocketMQ 的網絡模型比 RPC 更加複雜,是一套有狀態的網絡交互,這主要體現在兩點:
- RocketMQ 目前的網絡調用高度依賴於有狀態的 IP;
- 原生 SDK 中消費時的負載均衡使得每個消費者的狀態不可以被忽略。
對於前者,使得現有的 SDK 完全無法使用分區順序消息,因為發送請求和消費請求 RPC 的內容中並不包含 IP/(BrokerName + BrokerId) 等信息,導致使用了 Mesh 之後的 SDK 不能保證發送和消費的 Queue 在同一臺 Broker 上,即 Broker 信息本身在 Mesh 化的過程中被抹除了。當然這一點,對於只有一臺 Broker 的全局順序消息而言是不存在的,因為數據平面在負載均衡的時候並沒有其他 Broker 的選擇,因此在路由層面上,全局順序消息是不存在問題的。
對於後者,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 中原生的負載均衡策略提供了可能。
圖 2
圖 2 右側即為 Service Mesh 中 Pop Consumer 的消費情況,在 Envoy 中我們會忽略掉 SDK 傳來的 Queue 信息。
彈內海量的 Topic 路由信息
在集團內部,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 的特性。
Mesh 為 RocketMQ 帶來了什麼?
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
What's Next ?
目前 RocketMQ Filter 具備了普通消息的發送和 Pop 消費能力,但是如果想要具備更加完整的產品形態,功能上還有一些需要補充:
- 支持 Pull 請求:現在 Envoy Proxy 只接收 Pop 類型的消費請求,之後會考慮支持普通的 Pull 類型,Envoy 會將 Pull 請求轉換成 Pop 請求,從而做到讓用戶無感知;
- 支持全局順序消息:目前在 Mesh 體系下,雖然全局順序消息的路由不存在問題,但是如果多個 Consumer 同時消費全局順序消息,其中一個消費者突然下線導致消息沒有 ACK 而會導致另一個消費者的消息產生亂序,這一點需要在 Envoy 中進行保證;
- Broker 側的 Proxy:對 Broker 側的請求也進行代理和調度。
蜿蜒曲折的社區歷程
起初,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 的代碼由我和@叔田 共同完成,對於一個沒什麼開源經驗的我來說,為這樣的熱門社區貢獻代碼是一次非常寶貴的經歷,同時也感謝叔田在此過程中給予的幫助和建議。
相關鏈接
- Official docs for RocketMQ filter
- Pull request of RocketMQ filter
- RocketMQ filter's issue
- On-demand CDS pull request for Envoy
- First version of RocketMQ filter's proposal
阿里巴巴雲原生中間件團隊招人啦
這裡有足夠多的業務場景、足夠大的消息生態、足夠深的分佈式技術等著大家前來探索,如果你滿足:
- 至少精通一種編程語言,Java 或 C++;
- 深入理解分佈式存儲理論,微服務優化實踐;
- 計算機理論基礎紮實,例如對操作系統原理、TCP/IP 等有比較深入的理解;
- 具有獨立設計一款生產環境高可用高可靠的中間件能力,例如 RocketMQ;
- 熟悉高併發、分佈式通信、存儲、開源中間件軟件等相關技術者更佳。
歡迎加入並參與新一代雲原生中間件建設!簡歷提交地址:[email protected]。
課程推薦
為了更多開發者能夠享受到 Serverless 帶來的紅利,這一次,我們集結了 10+ 位阿里巴巴 Serverless 領域技術專家,打造出最適合開發者入門的 Serverless 公開課,讓你即學即用,輕鬆擁抱雲計算的新範式——Serverless。
點擊即可免費觀看課程:https://developer.aliyun.com/learning/roadmap/serverless
“阿里巴巴雲原生關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,做最懂雲原生開發者的公眾號。”