消息隊列二十年

来源:https://www.cnblogs.com/88223100/archive/2023/08/03/Message-queue-for-twenty-years.html
-Advertisement-
Play Games

2020 年我有幸加入騰訊 tdmq 初創團隊,當時 tdmq 還正在上雲公測階段,我第一次從一個使用工具的人轉變成了開發工具的人, 這個過程使我沉澱了很多消息隊列知識與設計藝術。 後來在業務中台的實踐中,也頻繁地使用到了 MQ,比如最常見的消息推送,異常信息的重試等等, 過程中也對消息隊列有了更加 ...


2020 年我有幸加入騰訊 tdmq 初創團隊,當時 tdmq 還正在上雲公測階段,我第一次從一個使用工具的人轉變成了開發工具的人,
這個過程使我沉澱了很多消息隊列知識與設計藝術。
後來在業務中台的實踐中,也頻繁地使用到了 MQ,比如最常見的消息推送,異常信息的重試等等,
過程中也對消息隊列有了更加深刻的瞭解。
此篇文章,我會站在一個時間維度的視角上去講解這二十年每款 MQ 誕生的背景以及解決了何種問題。

1. 消息隊列發展歷程

2003 至今有很多優秀的消息隊列誕生,其中就有被大家所熟知的就是 kafka、阿裡自研的 rocketmq、以及後起之秀 pulsar。首先我們先來瞭解一下每一時期消息隊列誕生的背景以及要解決的核心問題是什麼?

圖片

如圖所示,我把消息隊列的發展切分成了三個大的階段。

第一階段:解耦合

就是從 2003 年到 2010 年之前,03 年可以說電腦軟體行業剛剛興起,解決系統間強耦合變成了程式設計的一大難題,這一階段以 activemq 和 rabbitmq 為主的消息隊列致力於解決系統間解耦合和一些非同步化的操作的問題,這也是所有消息隊列被使用的最多的功能之一。

第二階段:吞吐量與一致性

在 2010 年到 2012 年期間,大數據時代正式到來,實時計算的需求越來越高,數據規模也越來越大。由於傳統消息隊列已無法滿足大數據的需求,消息隊列設計的關鍵因素逐漸轉向為吞吐量和併發程度。在這一背景下,Kafka 應運而生,併在日誌收集和數據通道領域占據了重要地位。

然而,隨著阿裡電商業務的興起,Kafka 在可靠性、一致性、順序消息、事務消息支持等方面已經無法滿足阿裡電商場景的需求。因此,RocketMQ 誕生了,阿裡在自研消息隊列的過程中吸收了 Kafka 的很多設計理念,如順序寫盤、零拷貝、end-to-end 壓縮方式,併在此基礎上解決了 Kafka 的一些痛點問題,比如強依賴 Zookeeper。後來,阿裡將 RocketMQ 捐贈給了 Apache,並最終成為了 Apache RocketMQ。

第三階段:平臺化

"沒有平臺的產品是沒用的,再精確一點,去平臺化的產品總是被平臺化的產品所取代" 這句話並不是我說的,而是來自一篇來自 2011 年的文章《steve 對亞馬遜和 google 的吐槽》( https://coolshell.cn/articles/5701.html 推薦閱讀)

2012 以後,隨著雲計算、k8s、容器化等新興的技術興起,如何把基本底層技術能力平臺化成為了眾多公司的攻堅方向,阿裡雲、騰訊雲、華為雲的入場都證明瞭這點,在這種大背景下,Pulasr 誕生了。

雅虎起初啟動 pulsar 項目是為瞭解決以下三個問題:

  • 公司內部多團隊重覆建造輪子。

  • 當前主流 mq 的租戶隔離機制都支持的不是很好。

  • 數據遷移、恢復、故障轉移個個都是頭疼的問題,消息隊列運維成本極高。

    這三個問題的答案都指向了一個方向:平臺化。

 

2. 消息隊列的通用架構及基本概念

第一節我們從時間線上介紹了主流的消息隊列產生的背景,這一小節我們先從生活場景入手,理解消息隊列最最基本的概念,

主題(topic)生產者(producer)消費者(consumer)

場景:食堂吃飯

我們可以把吃飯抽象成三步

  • 第一步:當你進入飯堂,首先你想的是我今天吃什麼,選擇合適的檔口,比如有米飯、有面、有麻辣香鍋。這裡的米飯、面、麻辣香鍋就是 topic 主題的概念
  • 第二步:當你選擇了面檔,下一步就是排隊,排隊預設的就是站在一個隊伍的隊尾,你加入隊伍的這一過程統稱為入隊,此時對於 MQ 則是成功生產了一條消息。
  • 第三步:經過了等待,終於排到了你,並把飯菜成功拿走。這個過程稱為出隊,檔口相當於消費者,消費了"你"這一條信息。

通過這個例子,你可以很好的理解,主題(topic)、生產者(producer)、消費者(consumer)這三個概念。

分區(partition)

分區概念是電腦世界對真實世界很好的一層抽象,理解了分區,對於理解消息隊列極其重要。如果你申請過雲上的消息隊列,平臺會讓你填寫一個分區大小的參數選項,那分區到底是什麼意思,我們接著向下看。

我們還是舉學習食堂的例子,假如有一天學校大面積擴招,一下次多來了一萬名學生,想象一下,你每次去食堂吃面,排上喜歡吃的東西,估計要等個一個小時,學校肯定也會想辦法,很簡單,就是把一個檔口變成多個檔口,對食堂進行擴建(擴容)。擴容前,賣面的檔口只有一個,人多人少你都要排隊。擴容後,你只要選擇(路由)一個人少的檔口就 ok。這裡多個隊伍就是多個分區。當理解了分區,就可以很好理解:分區使得消息隊列的寫吞吐量有了橫向擴展的能力,這裡也是 kafka 為什麼可以高吞吐的本質原因。

 

3. 主流消息隊列存儲分析

特性與性能是存儲結構的一種顯化表現。特性是表相,存儲才是本質。我們要搞清楚每款消息的特性,很有必要去瞭解它們在架構上的設計。這章節,我們先會去介紹 kafka、rocketmq、pulsar 各自的架構特點,然後再去對比架構上的不同帶來了什麼功能上的不同。

3.1. kafka

架構圖

對於 kafka 架構,需要首先說明的一點,kafka 的服務節點並沒有主從之分,主從的概念是針對 topic 下的某個 partition。對於存儲的單位,巨集觀上來說就是分區,通過分區散落在各個節點的方式的不同,可以組合出各種各樣的架構圖。以下是生產者數量為 1 消費者數量為 1 分區數為 2 副本數為 3 服務節點數為 3 架構圖。圖中兩塊綠色圖案分別為 topic1-partition1 分區和 topic1-partition2 分區,淺綠色方塊為他們的副本,此時對於服務節點 1 topic1-partition1 就是主節點,服務節點 2 和 3 為從節點;但是對於服務節點 2 topic1-partition2 有是主分區,服務節點 1 和服務節點 3 變成了從節點。講到這裡,想必你已經對主從架構有了一個進一步的理解。

圖片

我們先來看看消息隊列的大致工作流程。

  1. 生產者 、消費者首先會和元數據中心(zookeeper)建立連接、並保持心跳,獲取服務的實況以及路由信息。
  2. 消息會被 send 到 topic 下的任一分區中(這裡通過演算法會保證每個 topic 下的分區儘可能均勻),一般情況下,信息需要落盤才可以給上游返回 ack,保證了宕機後的信息的完整性。在信息寫成功主分區後,系統會根據策略,選擇同步複製還是非同步複製,以保證單節點故障時的信息完整性。
  3. 消費者此時開始工作,拉取響應的信息,並返回 ack,此時 offset+1。
好的設計

下來我們來瞭解一下 kafka 架構優秀的設計理念。

  • 磁碟順序寫盤。

Kafka 在底層設計上強依賴於文件系統(一個分區對應一個文件系統),本質上是基於磁碟存儲的消息隊列,在我們固有印象中磁碟的讀寫速度是非常慢的,慢的原因是因為在讀寫的過程中所有的進程都在搶占“磁頭”這把鎖,磁頭在讀寫之前需要將其移動到合適的位置,這個“移動”極其耗費時間,這也就是磁碟慢的原因,但是如何不用移動磁頭呢,順序寫盤就誕生了。

Kafka 消息存儲在分區中,每個分區對應一組連續的物理空間。新消息追加到磁碟文件末尾。消費者按順序拉取分區數據消費。Kafka 的讀寫是順序的,可以高效地利用 PageCache,解決磁碟讀寫的性能問題。

以下是一張磁碟、ssd、記憶體的寫入性能對比圖,我們可以明顯的看出順序寫入的性能快於 ssd 的順序/隨機寫,與記憶體的順序/隨機性能也相差不大,這一特性非常重要,很多組件的底層存儲設計都會用到這點,理解好這點對理解消息隊列尤為重要。(推薦閱讀《 The Pathologies of Big Data 》 https://queue.acm.org/detail.cfm?id=1563874 ))

圖片
一些問題

任何事物都有兩面性,順序寫盤的設計也帶來一些其他的問題。

  • topic 數量不能過大

kafka 的整體性能收到了 topic 數量的限制,這和底層的存儲有密不可分的關係,我們上面講過,當消息來的時候,底層數據使用追加寫入的方式,順序寫盤,使得整體的寫性能大大提高,但這並不能代表所有情況,當我們 topic 數量從幾個變成上千個的時候,情況就有所不同了,如下圖所示。

圖片

以上左圖代表了,隊列中從頭到尾的信息為:topic1、topic1、topic1、topic2,在這種情況下,很好地運用了順序寫盤的特性,磁頭不用去移動,但是對於右邊圖的情況,隊列中從頭到尾的信息為:topic1、topic2、topic3、topic4,當隊列中的信息變的很分散的時候,這個時候我們會發現,似乎沒有辦法利用磁碟的順序寫盤的特性,因為每次寫完一種信息,磁頭都需要進行移動,讀到這裡,你就很好理解,為什麼當 topic 數量很大時,kafka 的性能會急劇下降了。

當然會有小伙伴問,沒有其他辦法了嗎,當然有。我們可以把存儲換成速度更快 ssd 或者針對每一個分區都搞一塊磁碟,當然這都是錢!很多時候,系統的 6 個 9、7 個 9,並不是有多好的設計,而是用真金白銀換來的,這是一種 trade off,失去什麼得到什麼,大家可以對比看看自己的系統,大多數情況是什麼換什麼。

3.2. rocketmq

架構圖

以下是 rocketmq 雙主雙從的架構,對比 kafka,rocketmq 有兩點很大的不同:

  1. 元數據管理系統,從 zookeeper 變成了輕量級的獨立服務集群。
  2. 服務節點變為 多主多從架構。
圖片
zookeeper 與 namesrv

kafka 使用的 zookeeper 是 cp 強一致架構的一種,其內部使用 zab 演算法,進行信息同步和容災,在信息量較小的情況下,性能較好,當信息交互變多,因為同步帶來的性能損耗加大,性能和吞吐量降低。如果 zookeeper 宕機,會導致整個集群的不可用,對於一些交易場景,這是不可接受的,為了提高大數據場景下,消息發現系統的可用性與整體的吞吐量,相比 zookeeper,rocketmq 選擇了輕量級的獨立伺服器 namesrv,其有以下特點:

  1. 使用簡單的 k/v 結構保存信息。
  2. 支持集群模式,每個 namesrv 相互獨立,不進行任何通信。
  3. 數據都保存在記憶體當中,broker 的註冊過程通過迴圈遍歷所有 namesrv 進行註冊。
局部順序寫(kafka) 與 完全順序寫(rocketmq)

kafka 寫流程中會把不同分區寫入對應的文件系統中,其設計理念保證了 kafka 優秀的水平擴容能力。RocketMQ 的設計理念則是追求極致的消息寫,將所有的 topic 消息存儲在同一個文件中,確保所有消息發送時按順序寫文件,盡最大能力確保消息發送的高可用性與高吞吐量,但是有利有弊,這種 topic 共用文件的設計會使得 rocketmq 不支持刪除指定 topic 功能,這也很好理解,對於某個 topic 的信息,在磁碟上的表現是一段非連續的區域,而不像 kafka,一個 topic 就是一段連續的區域。如下圖所示。

圖片
rocketmq 存儲結構

下麵我們來重點介紹 rocketmq 的存儲結構,通過對存儲結構的瞭解,你將會更好的對讀寫性能有一個更深的認識。

不同 topic 共用一個文件的模式帶來了高效的寫性能,但是單看某一 topic 的信息,相對於磁碟上的表現為非連續的若幹片段,這樣使得定位指定 topic 下 msg 的信息,變成了一個棘手的問題。

rocketmq 在生產過程中會把所有的 topic 信息順序寫入 commitlog 文件中,消費過程中,使用 ConsumeQueue、IndexFile 索引文件實現數據的高效率讀取。下麵我們重點介紹這三類文件。

  • Commitlog

從物理結構上來看,所有的消息都存儲在 CommitLog 裡面,單個 CommitLog 文件大小預設 1G,文件名長度為 20 位,左邊補零,剩餘為起始偏移量。

比如 00000000000000000000 代表了第一個文件,起始偏移量為 0 文件大小為 1G=1073741824;當第一個文件寫滿了,第二個文件為 00000000001073741824,起始偏移量為 1073741824,以此類推。消息主要是順序寫入日誌文件,當文件滿了,寫入下一個文件。CommitLog 順序寫,可以大大提高寫入效率。

  • ConsumeQueue (索引文件 1)

ConsumeQueue 文件可以看成是基於 topic 的 commitlog 索引文件。Consumer 即可根據 ConsumeQueue 來查找待消費的消息。

因為 ConsumeQueue 里只存偏移量信息,大部分的 ConsumeQueue 能夠被全部讀入記憶體,速度極快。在定位 msg 信息時直接讀取偏移量,在 commitlog 文件中使用二分查找到對應的全量信息即可。

  • IndexFile(索引文件 2)

IndexFile 是另一種可選索引文件,提供了一種可以通過 key 或時間區間來查詢消息的方法。IndexFile 索引文件其底層實現為 hash 索引,Java 的 HashMap,可以快速通過 key 找到對應的 value。

講到這裡,我們在想想 kafka 是怎麼做的,對的,kafka 並沒有類似的煩惱,因為所有信息都是連續的!以下是文件在目錄下的存儲示意圖。

圖片

3.3. pulsa

架構圖(分層+分片)
圖片

pulsar 相比與 kafka 與 rocketmq 最大的特點則是使用了分層和分片的架構,回想一下 kafka 與 rocketmq,一個服務節點即是計算節點也是服務節點,節點有狀態使得平臺化、容器化困難、數據遷移、數據擴縮容等運維工作都變的複雜且困難。

分層:Pulsar 分離出了 Broker(服務層)和 Bookie(存儲層)架構,Broker 為無狀態服務,用於發佈和消費消息,而 BookKeeper 專註於存儲。

分片 : 這種將存儲從消息服務中抽離出來,使用更細粒度的分片(Segment)替代粗粒度的分區(Partition),為 Pulsar 提供了更高的可用性,更靈活的擴展能力。

服務層設計

Broker 集群在 Pulsar 中形成無狀態服務層。服務層是“無狀態的”,所有的數據信息都存儲在了 BookKeeper 上,所有的元信息都存儲在了 zookeeper 上,這樣使得一個 broker 節點沒有任何的負擔,這裡的負擔有幾層含義:

  1. 容器化沒負擔,broker 節點不用考慮任何數據狀態帶來的麻煩。
  2. 擴容、縮容沒負擔,當請求量級突增或者降低的同時,可以隨時的添加節點或者減少節點以動態的調整資源,使得整體在一種“合適”的狀態。
  3. 故障轉移沒負擔,當一個節點宕機、服務不可用時,可以通快速地轉移所負責的 topic 信息到別的基節點上,可以很好做到故障對外無感知。
圖片
存儲層設計

pulsar 使用了類似於 raft 的存儲方案,數據會併發的寫入多個存儲節點上,下圖為四存儲節點、三副本架構。

圖片

broker2 節點當前需要寫入 segment1 到 segment4 數據,流程為:segment1 併發寫入 b1、b2、b3 數據節點、segment2 併發寫入 b2、b3、b4 數據節點、segment3 併發寫入 b3、b4、b1 數據節點、segment4 併發寫入 b1、b2、b4 數據節點。這種寫入方式稱為條帶化的寫入方式、這種方式潛在的決定了數據的分佈方式、通過路由演算法,可以很快的找到對應數據的位置信息,在數據遷移與恢復中起到重要的作用。

擴容

當存儲節點資源不足的時候,常規的運維操作就是動態擴容,相比 kafka 與 rocketmq、pulsar 不用考慮原數據的"人為"搬移工作,而是動態新增一個或者多個節點,broker 在寫入數據時通過路有演算法優先寫入資源充足的節點,使得整體的資源利用力達到一個平衡的狀態,如圖所示。

圖片

以下是一張 kafka 分區和 pulsar 分片的一張對比圖,左圖是 kafka 的數據存儲特點,因為數據和分區的強綁定,導致了第三艘小船沒有任何的數據,而相比 pulsar,數據不和任何存儲節點綁定,而是實時的動態寫入,從數據分佈和資源利用來說,要做的更好。

圖片
容災

當 bookie4 存儲節點宕機不可用時,如何恢復節點數據?這裡只需要增加新的存儲節點,並且拷貝 bookie2 與 bookie3 上的數據即可,這個過程對外是無感知的,實現了平滑切換,如圖所示。

圖片

4. 一些想法

縱觀消息隊列的發展、技術的革新總是解決了某些問題、每種設計的背後都有著一種天然的平衡 ,無論優劣,針對不同的場景,選擇不同的產品,才是王道。

作者:blithe

本文來自博客園,作者:古道輕風,轉載請註明原文鏈接:https://www.cnblogs.com/88223100/p/Message-queue-for-twenty-years.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 我們基於 canvas 實現了一款簡單的塗鴉面板,用於在網頁上進行繪圖和創作。此篇文章用於記錄柏成從零開發一個canvas塗鴉面板的歷程。 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 完整效果 對基本的表單樣式進行設置 這裡設置了基本的表單樣式,外層用了div進行包裹,重點是運用了兩個i元素在後期通過css樣式勾畫出一條線沒在聚焦文本框的時候線會過度成一個對話框,掩蓋掉原本的對話框的形式,很好的一個創意 <body> ...
  • 防抖函數和節流函數是工作中兩種常用的前端性能優化函數,今天我就來總結一下什麼是防抖和節流,並詳細說明一下如何在工作中應用防抖函數和節流函數 ...
  • ###HTML標簽 ####表格標簽 表格的主要作用: 表格主要用於**顯示、展示數據**,因為它可以讓數據顯示的非常的規整,可讀性非常好。特別是後臺展示數據的時候,能夠熟練運用表格就顯得很重要。一個清爽簡約的表格能夠把繁雜的數據表現得很有條理。 1.``用來定義表格的標簽。 2.``用來定義行的標 ...
  • 一、分支相關操作 1.創建本地分支 使用以下命令創建一個本地分支 git branch <本地分支名> 使用以下命令創建一個本地分支且新建分支從特定分支拉取代碼 git branch <本地分支名> origin/<遠程分支名 or 本地分支名> 2.創建本地分支並切換 使用以下命令創建一個本地分支 ...
  • Nuxt.js是一個基於Vue.js的通用應用框架,主要關註的是應用的UI渲染,利用Vue.js開發服務端渲染的應用所需要的各種配置。 ...
  • 問題描述:初始化渲染後 tabs的下劃線沒有居中對其,出現異位。 問題分析: 網上很多大佬分析過出現原因了 記錄下解決的過程: 在各個論壇搜集到解決方案都暫時無效 有使用v-if重新渲染的 有給類賦值偏移值的 有強行轉換px的 因為各種原因這些方法在自己身上沒有奏效所以記錄下自己解決方案 偏移問題會 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 介紹 canvas:使用腳本 (通常為 JavaScript) 來繪製圖形的 HTML 元素。 本人遍歷了以下兩份文檔,學習完就相當於有了筆和紙,至於最後能畫出什麼,則需要在 canvas 應用方面進一步學習。 MDN 的 Canvas ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...