作者簡介
冉小龍-騰訊雲中介軟體團隊研發工程師,Apache Pulsar committer、RoP 作者及 Maintainer、Apache BookKeeper contributor,Apache Pulsar Go client、Apache Pulsar Go Functions、StreamNative/pulsarctl 作者。
摘要
日前,騰訊雲中介軟體團隊聯合 StreamNative 團隊正式釋出了 RoP 0.2.0 版本,該版本在架構上全新升級,使用者在使用中可以完全避免訊息丟失、訊息重複消費、只能消費一部分 Partition 的資料等問題。
RoP 的定義
與 KoP、MoP 和 AoP 相似,RoP 是一種可插拔的協議處理外掛。
將 RoP 協議處理外掛新增到現有 Pulsar 叢集后,使用者無需修改程式碼,便能將現有的 RocketMQ 應用程式和服務遷移到 Pulsar,同時還能使用 Pulsar 的強大功能,例如:
- 計算與儲存分離
- 多租戶
- 跨地域複製
- 分層分片
- 輕量化計算框架 -- Pulsar Functions
- ......
釋出 RoP 0.2.0
2021 年 5 月 17 日,騰訊雲中介軟體團隊向社群貢獻了 RoP 0.1.0 的 beta 版本,RoP(RocketMQ on Pulsar) 是 將 RocketMQ 協議處理外掛引入 Pulsar Broker,這樣 Pulsar 即可支援原生的 RocketMQ 協議,RocketMQ 使用者可以無縫遷移到 Apache Pulsar。
今天,我們重磅釋出 RoP 0.2.0,該版本在架構上全新升級,在功能和穩定性上得到了更大的提升。提供了 ACL 鑑權和驗證的功能,可以更好的確保使用者資料的安全性,同時允許使用者對 Partitioned Topic 進行擴容,可以獲得更好的併發寫入能力, 並且完善了 RocketMQ 原生的管控端介面,可以更好的對服務進行處理和監控。
最新功能優化
在 0.2.0 版本中,騰訊雲中介軟體團隊在 0.1.0 的架構上進行全新設計,對 MessageID、訊息路由模型進行重構,確保不同場景下 RoP 訊息的準確性。
主要有以下三點優化內容:
1. 支援 RoP ACL 功能
ACL 機制是 RocketMQ 社群自帶的一個能力,可以很好的對使用者的資料進行鑑權和認證。RoP 0.2.0 版本複用了 RocketMQ 自身的 Hook 實現,利用 Pulsar 自身的鑑權機制,實現了對使用者資料進行鑑權和認證的功能。
RoP ACL 的使用方式依舊延續了 RocketMQ 的使用方式,只需定義ACL_ACCESS_KEY
和 ACL_SECRET_KEY
欄位,然後利用 RocketMQ 的 ACLRPCHook 函式載入即可,這樣可以確保使用者儘可能少的改動客戶端的業務程式碼邏輯。
具體程式碼示例如下:
private static final String ACL_ACCESS_KEY = "eyJrZXlJZCI6InJvY2tldG1xLW13bmI3bWFwMjhqZSIsImFsZyI6IkhTMjU2In0."
+ "eyJzdWIiOiJyb2NrZXRtcS1td25iN21hcDI4amVfdGVzdCJ9.BDOjqqY25a6apnZTMZCqg0I0pxVFcqz7fvZbaTqkf5U"; // token
private static final String ACL_SECRET_KEY = "rop";
public static void producer() throws MQClientException {
DefaultMQProducer producer = new DefaultMQProducer("rocketmq-mwnb7map28je|nit", "ProducerGroupName",
getAclRPCHook());
...
}
static RPCHook getAclRPCHook() {
return new AclClientRPCHook(new SessionCredentials(ACL_ACCESS_KEY, ACL_SECRET_KEY));
}
- ACL_ACCESS_KEY:即使用者在 Namespace 級別下建立的 Token。
- ACL_SECRET_KEY:固定值,在 RoP 內部解析時,不會使用到這個欄位。
2. 重構 MessageID
RocketMQ 與 Kafka 類似,都是使用 64 位的 Offset 來唯一標識一條訊息,但是在 Pulsar 中,使用 64 位的 LedgerID、64 位的 EntryID 來唯一標識一條訊息。針對這個問題,在 RoP 0.1.0 中,我們使用如下的形式來構造 MessageID 物件:
- PartitionID: 8 位,可以允許一個 Topic 最多建立 256 個 Partitions
- LedgerID: 32 位
- EntryID: 24 位
使用如上的方式可能存在 MessageID 的訊息精度丟失,在系統執行一段時間之後,無法繼續建立出新的 LedgerID,導致整個叢集的服務對外不可用的情況。這個問題與早期的 KoP 版本所面臨的是同樣的困境,所以在 RoP 0.2.0 中,我們採用了和 KoP 相同的處理方式,使用PIP 70: Introduce lightweight broker entry metadata 的處理思路,在 Broker 的協議頭中,附加了一個 64 位的 index/publish-time 欄位,這樣無需在客戶端側進行協議的解析即可在每一條訊息中附加一個 64 位的欄位來使用。
PIP-70 是使用外掛的方式進行載入的,所以在服務啟動時,我們需要做如下配置:
brokerEntryMetadataInterceptors=org.apache.pulsar.common.intercept.AppendIndexMetadataInterceptor
Note: Broker Entry Metadata 是在 Pulsar 2.8.0 的版本中才支援的,所以需要確保 Pulsar Broker 的版本在 2.8.0 及以上。
需要說明的是,RocketMQ 和 Kafka 在 Offset 的使用方式上又有所不同。RocketMQ 中有兩個 Offset,一個是 Queue Offset,用來表示訊息在 MessageQueue 中的位置,MessageQueue 本質上是一個陣列,一條訊息進來陣列的下標就會 +1。一個是 CommitLog Offset 用來表示訊息儲存在 CommitLog 中的位置,訊息儲存是由 ConsumeQueue 和 CommitLog 配合完成,ConsumeQueue 是邏輯佇列,CommitLog 是真正儲存訊息檔案的,ConsumeQueue 儲存的是指向物理儲存的地址。Topic 下的每個 MessageQueue 都有對應的 ConsumeQueue 檔案,內容也會被持久化到磁碟。
所以,在 MessageID 重構的實現中,區別於 Kafka 中只有一個全域性的 Offset 來標識訊息的唯一性,在 RoP 中需要針對這兩種 Offset 的情況分別進行處理,具體如下:
- RESERVED_BITS: 1 位元組的保留位,避免第一個位元組出現負數等情況導致 Offset 計算有誤。
- RETRY_TOPIC_TAG_BITS:1 位元組的標記為,用來標識這個 Topic 是否為 Retry 型別的 Topic
- PULSAR_PARTITION_ID_BITS:10 位元組的 Partition Num,用來記錄一個 PartitionedTopic 下有多少個 Partitions,最大支援 1024 個 Partitions。
- OFFSET_BITS:52 位元組用來標識訊息的 Offset。
3. 重構訊息的路由模型
在 RoP 0.1.0 的版本中,在訊息路由的實現上,RocketMQ 和 Pulsar 都是首先通過 Topic 查詢 的操作找到對應的 Owner Broker 節點,然後將該 Broker 的地址返回。但是在這個動作中,忽略了一個重要的問題,即 RocketMQ 與 Kafka 和 Pulsar 都是不同的,它的 Queue 不是全域性唯一的。
RocketMQ 路由協議主要包括兩部分:
- Broker 服務的 IP 地址資訊;
- 某個 Broker 上對應的 Topic 分割槽總數以及分割槽可讀寫資訊。
在 RocketMQ 路由協議中,沒有全域性標識 Topic 的分割槽的唯一 ID(例如在 Pulsar/Kafka 中,分割槽 ID 在叢集中是唯一的);而在 RocketMQ 中,分割槽路由資訊是由 Broker 標識加上該 Broker 上的順序從 0→N 的 Index 來標識 Topic 的分割槽。
因此 RocketMQ 協議中,客戶端只需要獲取到 Topic 對應 Broker 上分割槽總數,就能通過計算獲得該 Broker 上分割槽的 ID;所有的請求都是基於【Broker-Tag】+【Broker-Topic-Seq】構建唯一路由查詢原語來請求服務。簡單來說:RocketMQ 的分割槽是有狀態的,他繫結在特定的 Broker 之上;分割槽一旦分配在某個 Broker 上,終身與之相關且不能遷移。客戶端解析分割槽路由資訊是通過計算得到;比如:某個 TopicA 有 5 個分割槽,分別落在 BrokerA 和 BrokerB 上,BrokerA 有 3 個,BrokerB 有 2 個;那麼協議記錄為(BrokerA,3)(BrokerB,2),客戶端通過計算就得到全部的分割槽資料:
- BrokerA-TopicA-0,BrokerA-TopicA-1,BrokerA-TopicA-2
- BrokerB-TopicA-0,BrokerB-TopicA-1
由於上面的路由關係的原因,所以沒有辦法通過 GET_ROUTEINTO_BY_TOPIC
這個協議請求去和 Pulsar 的 查詢 協議去做對映。本質原因是像 Kafka/Pulsar 這種,它的 Partition 資訊是全域性唯一的,在執行 Topic 路由策略之後,能準確的返回某一個 Topic 的 Partition 所對應的 Owner Broker 是誰。但是 RocketMQ 的 Topic 路由返回的是兩個欄位,一個是 Broker Name,一個是 Queue 的數量。具體的 QueueID,是 Client 根據 Broker 返回的數量固定的從 0 開始遞增計算。所以在 Topic 的路由對映中,RocketMQ 和 Pulsar 自身的路由協議沒辦法一一對映。為了解決這個問題,在 RoP 0.2.0 中,抽象了一層 Proxy 用來維護 Topic 與 Broker 之間的對映關係。為了達到這個目的,這裡主要有以下幾方面的事情需要考慮:
- 這些對映關係儲存在哪裡?
- 如何分配路由關係?
- 當路由關係發生變化之後,如何處理?
針對第一個問題,綜合考量,我們選擇將路由的對映關係儲存到 ZooKeeper 叢集中來,因為當前 RoP 的服務本身也需要依賴 ZooKeeper 叢集,不會引入新的元件;其次 ZooKeeper 自身的一致效能力能很好的滿足這個場景需求。
針對第二個問題,我們是在 RoP 介面建立分割槽主題的同時,依次 查詢 各個分割槽所在的 Broker 節點,依照初始主題所在節點資訊為基準,將對映關係寫入到 ZooKeeper 叢集中。這樣做的好處在於:
- 複用 Pulsar 自身分割槽分配機制計算的結果,實現簡單。
- 初始分配後,虛擬節點和物理節點處於一個節點上,效能好。
- 如果配合路由關係重平衡能力,可以實現最優效能。
針對第三個問題,我們通過增加 Master-Slave 模式,可以減少單節點故障對系統的影響。ZooKeeper 後設資料如下,只需要增加 Broker 相關資訊,即可實現各個節點的互為主從關係,達到主節點不可用時從節點可以繼續提供服務。由於當前 Offset 資訊都儲存在 Compact Topic 中,全部節點同時訂閱,所以各個節點的後設資料可以保證一致,可以實現主從切換。下面是測試環境中部署 RoP 叢集中的路由對映關係:
所以,為了保證 RoP 叢集能有較好的容錯能力,在部署 RoP 叢集中建議使用偶數臺節點。可以通過如下引數配置決定當前 Master 節點有幾個 Slave 節點作為其備份節點:
RoPBrokerReplicationNum=2
假設有 6 臺 Broker 節點,RoPBrokerReplicationNum=2, 那麼就說明此時只有三臺 Master Broker 節點對外提供服務。但是對於 Pulsar 來說,Broker 節點之間是 對等 的,當建立 Topic 的時候,可能會分配到任意節點上,所以對於不在 Owner Broker 節點上的請求,在 RoP Proxy 層做了一層代理,會先對該 Topic 進行 查詢 的操作,然後將請求轉發到 Owner Broker 的節點上來返回。
未來規劃
為了更好的踐行開源協同和開源共建的理念,目前,上述功能均已貢獻回社群。除此之外針對 RocketMQ 商業版本的任意延遲訊息功能,騰訊雲中介軟體團隊也基於 Pulsar 原生的特性開發了相關的外掛來進行支援。RoP 的延遲訊息功能除了支援多級別的延遲訊息之外還具備支援任意延遲訊息的能力。
之後,騰訊雲中介軟體團隊將會在確保 RoP 專案穩定的同時,持續開發 RoP 相關的功能,諸如訊息軌跡,訊息查詢和回溯以及監控等能力,進一步完善 RoP 的功能以及周邊生態。
RoP 專案地址:https://github.com/streamnati...
特別鳴謝
感謝騰訊雲中介軟體團隊韓明澤和張勇華對本文提供的技術細節校驗和支援。
關注公眾號「Apache Pulsar」,獲取乾貨與動態
加入 Apache Pulsar 中文交流群 ??
點選連結 ,下載最新版本 RoP!