如何設計一個牛逼的訊息佇列?

架構擺渡人發表於2022-05-01

大家好,我是【架構擺渡人】,一隻十年的程式猿。這是訊息佇列的第一篇文章,這個系列會給大家分享很多在實際工作中有用的經驗,如果有收穫,還請分享給更多的朋友。

通過前面文章的學習,我們對訊息佇列的作用以及目前主流的一些訊息佇列中介軟體有了更深刻的瞭解。但是那些優秀的中介軟體都是別人寫出來的,如果你在面試的時候,面試官問你:如果讓你去設計一個訊息佇列,你打算怎麼做?如果你對訊息佇列了解的不徹底,那麼很有可能被這個問題問懵掉,最後支支吾吾的說不知道。

服務端

我們從日常使用訊息佇列來入手,看設計一個訊息佇列到底要有哪些關鍵的點。當你要用訊息佇列的時候首先肯定是下載部署包,然後部署在伺服器上。部署的這個程式我們就理解它是訊息佇列的服務端程式。在其他訊息佇列裡面都有一個固定的名稱:Broker

那麼為什麼需要Broker呢?

你的訊息要傳送出去,必然得有接收方,這個接收方就是Broker。Broker收到訊息後不是直接轉給消費方,而是要先落盤,儲存起來。這樣才能保證訊息不丟失,不影響業務。同時還有一些其他的業務操作,比如訊息的查詢。

儲存

既然說到儲存,我們做業務的時候,都會用三方儲存,也就是資料庫,比如Mysql。但是MQ的儲存,基本上都不會用三方儲存,而是直接採用寫磁碟的方式,也就是自己要設計要儲存格式,自己寫,自己解析等等一系列操作。

當然,也不是說不能用三方儲存去實現,下篇文章我們再給大家講講如何用資料庫做訊息佇列的儲存。用資料庫做儲存其實也就是利用已有的實現來解決複雜度,涉及到底層儲存這塊,而且還要考慮高效能,其實對技術要求很高的。

像RocketMQ中的儲存就涉及到CommitLog,ConsumeQueue,IndexFile等概念。最重要的是磁碟操作我們都知道很慢,而我們經常用的Mysql為了提高效能也是有一套很複雜的設計,比如redo log,buffer pool等,所以如果直接用資料庫做儲存,是不是相當於站在巨人的肩上去摘果實呢!

主從

我們設計了一個Broker,使用過程中萬一這個Broker掛掉了怎麼辦?這裡是不是得考慮下高可用性,所以Broker還需要有主從的設計。

主節點的資料會同步給從節點,主節點出問題後,從節點可以頂上來提供服務,同時從節點也可以提供讀的操作,為主節點減輕壓力。

分片

一個Broker是部署在某一臺伺服器上面,這個服務的磁碟儲存空間是有限制的,不可能無限擴容。所以當訊息量很大的時候,如果只是一直往機器的本地磁碟寫資料,最終會寫不進去的。

在設計的時候還要考慮資料分片的場景,一個Topic的資料可以分成很多份進行儲存,分別儲存在不同的Broker上,這樣當磁碟不夠的時候,可以通過增加Broker的節點來擴容。

那麼問題來了,客戶端寫入的時候怎麼知道這個Topic有哪些分片的儲存資訊,怎麼知道有哪些Broker是線上的呢?這就要引入另一個設計:註冊中心,在RocketMq中叫NameServer。

註冊中心

NameServer叫註冊中心或者路由中心都可以,本質上都一樣。Broker啟動的時候需要將自身的資訊告訴NameServer,同時也要保持一個心跳檢查,這樣NameServer才能知道Broker當前是否處於正常狀態。

NameServer也要支援水平擴充套件,這樣才能保證高可用性。既然要支援水平擴充套件,那麼必然得無狀態才行,但是NameServer本身就會儲存一些資料,比如Broker資訊。

這裡有幾個實現方式:

Broker啟動的時候輪流向所有的NameServer進行註冊,這樣每個NameServer中都有全量的資訊,即使某個節點掛了也不影響。RocketMQ就是使用的這種方式。

Broker啟動的時候只向某一臺NameServer進行註冊,立馬返回,然後NameServer之間再進行相互同步,Eureka就是使用的這種方式。

Broker啟動的時候只向某一臺NameServer進行註冊,NameServer會同步向其他的NameServer進行資料的同步操作,等待所有寫入成功或者半數寫入成功,然後再返回給客戶端。Zookeeper就是使用的這種方式。

SDK

服務端有了,還有一個必須要有的設計就是SDK了。應用程式通過依賴SDK就可以直接傳送訊息和消費訊息。SDK同時可以考慮支援多語言,這樣使用場景更廣泛。

SDK主要是用來跟Broker通訊的,所以對於網路通訊我們也要選擇一個合適的框架,比如Netty就非常合適,你要是覺得太難,直接用Http協議也可以,或者直接支援多協議,這些都是需要考慮的場景。

後臺管理

後臺管理可以實現很多治理的工作,方便我們在使用訊息佇列的時候去排查各種問題。

核心功能點:

  • 當前叢集狀態的檢視
  • 訊息的查詢
  • 訊息的消費軌跡查詢
  • 訊息的重複投遞
  • 訊息生產的監控大盤
  • 訊息消費的監控大盤
  • SDK消費執行緒數的動態調整
  • 等等

總結

本文只是簡單的給大家介紹了下設計一個訊息佇列需要做哪些核心的工作,看起來就幾個點而已。但是這幾個點你要真正的去寫程式碼實現難度是很大的。當然,我們其實也沒必要自己去造輪子,因為你造了也不一定能比目前主流在用的好,但是整體的架構我們還是要了解的,至於細節就看自己需不需要深入瞭解了。比如訊息儲存那塊,儲存格式是怎樣的?順序寫如何實現的?mmap技術如何應用的等等。

原創:架構擺渡人(公眾號ID:jiagoubaiduren),歡迎分享,轉載請保留出處。

相關文章