作者:田寄遠
JCQ 全名 JD Cloud Message Queue,是京東雲自研、具有 CloudNative 特性的分散式訊息中介軟體。 JCQ 設計初衷即為適應雲特性的訊息中介軟體;具有高可用、資料可靠性、副本物理隔離、服務自治、健康狀態彙報、少運維或無運維、容器部署、彈性伸縮、租戶隔離、按量付費、雲賬戶體系、授權等特性。
2017 年中開始開發 JCQ 1.0 版本,2018 年 11 月正式 GA 上線對外售賣,1.0 版本中 Topic 受限於單臺伺服器限制,滿足不了使用者超大規格 topic 的需求。
2019 年 4 月 JCQ 2.0 正式上線,主要新增特性是 topic 擴縮容能力、熱點 Topic 在 Broker 間的負載均衡、熱點 Broker 的流量轉移。
2019 年 7 月 JCQ 做了一次大的架構演進 —— 計算儲存分離,大版本號為 JCQ 3.0, 於 2019 年底上線。計算儲存分離對架構帶來了比較明顯的好處,解決了日常遇到許多的痛點問題。下文詳細介紹此次演進帶來的好處及解決的痛點問題。
在 JCQ2.0 中計算模組與儲存模組處於同一個程序,升級計算模組勢必將儲存模組一起升級。而儲存模組重啟是比較重的動作,需要做的工作有:載入大量資料、進行訊息資料與訊息索引資料比對、髒資料截斷等操作。往往修復計算模組一個小的 Bug,就需要做上述非常重的儲存模組重啟。而在現實工作中,大部分升級工作都是由於計算模組功能更新或 Bugfix 引起的。
為了解決這個問題, JCQ3.0 將計算模組、儲存模組獨立部署,之間通過 RPC 呼叫。各自升級互不影響。如下圖所示:
計算節點 Broker 只負責生產訊息、推播訊息、鑑權、認證、限流、擁塞控制、使用者端負載均衡等業務邏輯,屬於無狀態服務。比較輕量,升級速度快。
儲存節點 Store 只負責資料寫入、副本同步、資料讀取。因為業務邏輯簡單,功能穩定後,除優化外基本無需改動,也就無需升級。
JCQ 是共用訊息中介軟體,使用者申請的是不同規格 TPS 的 Topic,並不感知 cpu、memory、disk 等硬體指標。 所以,JCQ 服務方需要考慮如何合理的使用這些硬體指標。
JCQ 是容器部署,有多種型別的元件,這些元件對硬體的需求也是多樣化的,其中對資源消耗最多的是計算模組和儲存模組。在 JCQ2.0 版本計算模組和儲存模組部署在一起,選擇機型時要兼顧 cpu、memory、disk 等指標,機型要求單一,很難與其他產品線混合部署。即使是同一資源池,也存在因為排程順序,造成排程失敗的情況。如一臺機器剩餘資源恰好能排程一個需要大規格磁碟的 A 容器,但是因為 B 容器先被排程到這臺機器上,剩餘資源就不夠建立一個 A 容器,那這臺機器上的磁碟就浪費了。
JCQ3.0 後,計算節點 Broker,與儲存節點 Store 獨立部署。這兩個元件可以各自選擇適合自己業務的機型,部署在相應資源池中;最終,可以做到與其他產品混合部署,共用資源池水位,而不用獨自承擔資源水位線。
JCQ3.0 中計算節點 Broker 是無狀態服務,主從切換比較輕量,能在秒級完成故障轉移;且部署時考慮了物理裝置反親和,如跨 Rack、跨 AZ 部署。所以,可以在可用性、資源成本之間做一定的權衡;如可以使用 M:1 方式做高可用冷備,而不必 1:1 的比例高可用冷備,進而達到節省硬體資源的目的。
JCQ 1.0 設計之初就採用 Raft 演演算法,來解決服務高可用、資料一致性的問題。Message Log 與 Raft Log 有很多共同的特性,如順序寫、隨機讀、末端熱資料。所以,直接用 Raft Log 當成 Message Log 是非常合適的。
在 JCQ 演進中我們也發現了 Raft 本身的一些效能問題,如順序複製、順序 commit、有的流程只能用單執行緒處理等限制。針對這些問題,最直接有效的辦法就是擴充套件 Raft 的數目、擴充套件單執行緒流程數目,在一定數量級內,並行能力隨著 Raft Group 數目的增長,呈線性增長關係,稱之 MultiRaft,如下圖所示。
****
上圖中,每個 StoreNode 節點是一個獨立程序,內部有四組邏輯 RaftGroup(橙色的節點為 RaftGroup 的 Leader),各組 RaftGroup 之間是並行關係,可以做到 Group 間並行複製、並行 commit。
由於大量使用了 NIO,這些 RaftGroup 之間可以共用通訊執行緒池,擴充 RaftGroup 數目並不會帶來執行緒資源線性增長的問題。
在 JCQ3.0 中,Broker 為輕量的無狀態服務,在主從切換、故障恢復方面相對 2.0 更為輕量,本身能更快的恢復對外服務能力。
同時,Broker 將 Producer、Consumer 的連線請求,抽象為 PubTask 和 SubTask,後文統稱為 Task。Task 的概念非常輕量,僅描述 Client 與 Broker 的對應關係,由後設資料管理器 Manager 統一排程、管理。轉移 Task 只需要修改 Task 的內容,使用者端重新連線新 Broker 即可。
一般來說,Broker 的主要瓶頸在於網路頻寬。Broker 定期統計網路入口流量與出口流量,並上報給管理節點 Manager。Manager 根據入口流量、出口流量與頻寬閾值進行裁決,發現超過閾值後,通過一定策略將相應的 Task 轉移到較小負載的 Broker 上,並通知相應的 Producer 與 Consumer;Producer 與 Consumer 收到通知後,重新獲取 Task 的路由資訊,自動重連到新的 Broker 繼續進行生產、消費。
設想一個場景,有一個大規格的 topic,建立了 n 個消費組。消費總 TPS 是生產總 TPS 的 n 倍。增加消費組,會導致消費總 TPS 線性增長。到達一定消費組規模後,單 Broker 由於網路卡頻寬的原因,無法滿足這種高扇出的場景。單伺服器是無法解決這個問題。
在 JCQ 3.0 可以將這些不同的消費組對應的 SubTask 分散到若干個 Broker 上,每個 Broker 負責一部分 SubTask,單 Broker 從 Store 預讀訊息,將資料推播給 Consumer。這樣多個 Broker 共同完成所有消費組的訊息流量,共同作業一起提供高扇出的能力。
訊息中介軟體很大的特點是:大部分場景下,熱資料都在末端,而回溯幾天之前的訊息這個功能是不常用的。所以,就有冷熱資料之分。
JCQ 計算節點設計了一層儲存抽象層 Store Bridge 可以接入不同的儲存引擎,可以接入 Remote Raft Cluster,或者分散式檔案系統 WOS、或者 S3。甚者可以將冷資料定期從昂貴的本地盤解除安裝到廉價的儲存引擎上。
相對於 JCQ2.0,計算節點與儲存節點之間的通訊方式,由介面呼叫變為 RPC 呼叫,在延遲方面會有一定損失。經過測試,絕大部分延遲都在 1ms 左右,在大多數場景下 犧牲 1ms 左右的延遲並不會給業務帶來太大的影響。
JCQ 後續會主要在多協定相容,按需自動擴縮容、雲原生等方面演進。
JCQ 協定為私有協定,在引導使用者遷移方面有比較大的障礙。後續會抽離 JCQ Kernel,外部提供不同的協定接入層。方便使用者從其他 MQ 接入 JCQ。目前已相容 RocketMQ 協定,SQS 協定
JCQ 是共用訊息中介軟體,但缺少 Serverless 自動擴縮容的特性。每逢大促,如 618,雙 11,服貿會等重要活動。業務方很難預估自己的業務量峰值,或者估計不足,會造成 topic 限流等問題。如在保證 JCQ 服務本身能力情況下,能做到 topic 靈活的自動擴縮容,將對使用者有極大的幫助,起到真正的削峰填谷作用。
支援在 kubernetes 環境部署與交付,提供原生的 Operator,能夠快速的部署在 k8s 環境中,更好的交付私有云、混合雲專案。