天天看點

聊聊 RocketMQ 名字服務

作者:java小悠

NameServer 是專為 RocketMQ 設計的輕量級名字服務,它的源碼非常精簡,八個類 ,少于1000行代碼。

聊聊 RocketMQ 名字服務

這篇文章, 筆者會從基礎概念、Broker發送心跳包、NameServer 維護路由、Zookeeper vs NameServer 四個子產品揭秘名字服務的設計精髓。

1 基礎概念

聊聊 RocketMQ 名字服務

NameServer 是一個非常簡單的 Topic 路由注冊中心,其角色類似 Dubbo 中的 zookeeper ,支援 Broker 的動态注冊與發現。

RocketMQ 叢集工作流程:

1、NameServer 啟動服務,監聽 TCP 端口 , 叢集多節點之間無任何資訊互動,然後等待 Broker、Producer 、Consumer 連上來;

2、Broker 啟動後,每隔 30 秒向所有的 NameServer 發送心跳指令 ;

3、NameServer 接收到請求之後,儲存路由資訊在本地記憶體裡 ,将響應結果返給 Broker 服務;

4、Producer 啟動之後,會随機的選擇一個 NameServer ,并從 NameServer 中擷取目前發送的 Topic 存在哪些 Broker 上,輪詢從隊列清單中選擇一個隊列,然後與隊列所在的 Broker 建立長連接配接進而向 Broker 發消息;

5、Consumer 跟 Producer 類似,跟其中一台 NameServer 建立長連接配接,擷取目前訂閱 Topic 存在哪些 Broker 上,然後直接跟 Broker 建立連接配接通道,開始消費消息。

2 Broker發送心跳包

我們貼一段 Broker 發送心跳指令的源碼:

聊聊 RocketMQ 名字服務

1、Broker 會每隔 30 秒向所有的 NameServer 發送心跳指令 ;

使用 CountDownLatch 實作多線程同步,可以擷取發往所有的 NameServer 的心跳指令的響應結果

2、心跳指令包含兩個部分:請求頭和請求體

聊聊 RocketMQ 名字服務

3 NameServer 維護路由

NameServer 在接收到 Broker 發送的心跳請求之後,通過預設的處理器來處理請求,儲存路由資訊成功後,注冊成功狀态傳回給 Broker 服務。

源碼中,我們可以看到路由資訊儲存在 HashMap 中 。

聊聊 RocketMQ 名字服務

1、topicQueueTable:Topic 消息隊列路由資訊,包括 topic 所在的 broker 名稱,讀隊列數量,寫隊列數量,同步标記等資訊,rocketmq 根據 topicQueueTable 的資訊進行負載均衡消息發送。

2、brokerAddrTable:Broker 節點資訊,包括 brokername,所在叢集名稱,還有主備節點資訊。

3、clusterAddrTable:Broker 叢集資訊,存儲了叢集中所有的 Brokername。

4、brokerLiveTable:Broker 狀态資訊,NameServer 每次收到 Broker 的心跳包就會更新該資訊。

當 Broker 向 NameServer 發送心跳包(路由資訊),NameServer 需要對 HashMap 進行資料更新,但我們都知道 HashMap 并不是線程安全的,高并發場景下,容易出現 CPU 100% 問題,是以更新 HashMap 時需要加鎖,RocketMQ 使用了 JDK 的讀寫鎖 ReentrantReadWriteLock 。

下面我們看下路由資訊如何更新和讀取:

1、寫操作:更新路由資訊,操作寫鎖

聊聊 RocketMQ 名字服務

2、讀操作:查詢主題資訊,操作讀鎖

聊聊 RocketMQ 名字服務

我們可以将 NameServer 實作注冊中心的方式總結為:RPC 服務 + HashMap 存儲容器 + 讀寫鎖 + 定時任務 。

1、NameServer 監聽固定的端口,提供 RPC 服務

2、HashMap 作為存儲容器

3、讀寫鎖控制鎖的顆粒度

4、定時任務

  • 每個 Broker 每隔 30 秒注冊主題的路由資訊到所有 NameServer
  • NameServer 定時任務每隔10 秒清除已當機的 Broker , 判斷當機的标準是:目前時間減去 Broker 最後一次心跳時間大于2分鐘

4 Zookeeper vs NameServer

那為什麼 RocketMQ 不用 Zookeeper 做為注冊中心呢 ?

我們先溫習下 CAP 理論。

聊聊 RocketMQ 名字服務

CAP 理論是分布式架構中重要理論。

1、一緻性( Consistency ) :所有節點在同一時間具有相同的資料 ;

2、可用性( Availability ) :保證每個請求不管成功或者失敗都有響應 (某個系統的某個節點挂了,但是并不影響系統的接受或者送出請求) ;

3、分隔容忍( Partition tolerance ) :系統中任意資訊的丢失或失敗不會影響系統的繼續運作。 (在整個系統中某個部分,挂掉了,或者當機了,并不影響整個系統的運作或者說使用) 。

Zookeeper 是一個典型的 CP 注冊中心 ,通過使 ZAB 協定來保證節點之間資料的強一緻性。

筆者曾經遇到過一起神州專車服務當機事故,zookeeper 叢集不堪重負,一直在選主 。架構負責人修改了 zookeeper 的 jvm 參數,重新開機叢集後 , 才臨時解決了問題。

因為 MetaQ 叢集和服務治理共用一組 zookeeper 叢集 。

  • MetaQ 消費者負載均衡時,會頻繁的争搶鎖 ,同時也會頻繁的送出 offset ;
  • 專車的注冊服務也越來越多,注冊資訊通過Hession 序列化存儲在 zookeeper 的節點。

為了減少 zookeeper 叢集的性能壓力,架構團隊将 MetaQ 使用的 zookeeper 叢集獨立出來。

這次事故讓我認識到:Zookeeper 作為 CP 注冊中心,大規模使用場景下,它就變得很脆弱,我們要非常小心的使用。

淘寶中間件部落格出了一篇文章 : 阿裡巴巴為什麼不用 ZooKeeper 做服務發現 ?

文章有兩個觀點,筆者認為非常有借鑒意義。

1、當資料中心服務規模超過一定數量 ( 服務規模=F{服務 pub 數,服務 sub 數} ),作為注冊中心的 ZooKeeper 很快就會像下圖的驢子一樣不堪重負。

聊聊 RocketMQ 名字服務

2、可以使用 ZooKeeper,但是大資料請向左,而交易則向右,分布式協調向左,服務發現向右。

相比 ZooKeeper ,NameServer 是一個典型的 AP 注冊中心,它有如下優點:

1、代碼不到 1000 行,實作簡單,易于維護 ;

2、性能極好,除了網絡消耗,基本都是本地記憶體操作 ;

3、服務都是無狀态,且節點之間并不互動,運維簡單;

RocketMQ 的設計者之是以選擇自研名字服務,遵循着架構設計的準則,筆者總結為:簡單、高效、适當妥協。

原文連結:https://mp.weixin.qq.com/s/exKFh56By-PkMZmfyY0uRw

繼續閱讀