深入理解消息队列(场景,对比,原理和设计思想)
導語?|?消息隊列也通常稱為消息中間件,提到消息隊列,大部分互聯網人或多或少都聽過該名詞。對于后端工程師而言,更是日常開發中必備的一項技能。隨著大數據時代的到來,apache旗下的Kafka一度成為消息隊列的代名詞,提起消息隊列大家自然而然就想到了Kafka。然而消息隊列本身是工程領域內一種解決問題的通用方案。它的背后有著一些通用的設計思想和經典模型,這些是消息隊列的精髓和靈魂。它們獨立于任何一種消息隊列的具體實現(例如Kafka),但每種消息隊列(除了Kafka外,還有RocketMQ、Pulsar等)的實現中到處體現著這些設計思想。本文騰訊Cloud9項目組后臺開發工程師文小飛(Jaydenwen)主要從抽象層面來簡單談談消息隊列背后的一些設計思想,輔助理解消息隊列這一類組件。
本文主要解決三個問題:
1. 消息隊列適合什么場景?
2. 消息隊列有哪些主流產品、各自的優缺點?
3.?消息隊列背后的設計思想(整體核心模型、數據存儲考量、數據獲取方案對比、消費者消費模型)
一、消息隊列適合哪些場景
消息隊列:它主要用來暫存生產者生產的消息,供后續其他消費者來消費。它的功能主要有兩個:a.暫存(存儲)、b.隊列(有序:先進先出)。其他大部分場景對數據的消費沒有順序要求,主要用它的暫存能力?。從目前互聯網應用中使用消息隊列的場景來看,主要有以下三個:
異步處理數據
系統應用解耦
業務流量削峰
下面對上述每一種場景進行簡單描述。
(一)異步處理數據
第一個例子我們以現實生活中送快遞來類比,在該例子中我們把暫存快遞的快遞柜比作暫存數據的消息隊列。我們來看一下在現實生活中,沒有快遞柜時,快遞員把快遞送到目的地后,一般需要聯系收貨人來簽收快遞,如果收貨人此時有空,那一切都很順利。但如果收貨人此時不方便(開會、正在吃午飯、外出出差……)。那對于快遞員而言,就很尷尬,需要一直等待(開會or吃午飯)或者將快遞拿回去(外出出差),導致白跑一趟。這對于快遞員而言簡直太不友好。
從這兒可以看出,當快遞員送貨時,是一個同步狀態,即需要等待收貨人簽收后才能去送下一趟單子,對快遞員而言效率太低。上述例子雖然有點牽強,大家湊合理解,意思能大概理解到位就ok。
接著我們再來看一下,當有了快遞柜后,對于快遞員而言,每次需要送快遞時,只需要將快遞投擲到快遞柜,然后再通過短信或者電話通知收貨人具體的快遞信息即可。他就可以繼續去派送下一單。而對于收獲人而言,也可以根據具體方便的時間來取件。這樣一來,二者完全異步了,不用相互等待了。
在這個例子中,如果把快遞員比作生產者,收貨人比作是消費者,則快遞柜就類似于消息隊列。我們可以通過采用消息隊列來實現異步數據的處理。
(二)系統應用解耦
案例二,我們以目前最主流的推薦系統中內容的流轉來舉例。在推薦系統中當創作者發布了一條內容后,該內容會首先經過安全部分的相關審核,通過審核后的內容,通常需要進行內容入庫存儲、送入模型進行特征的計算和生成。
假如后期我們想提升推薦的效果,需要單獨構建一份冷啟動的推薦池,此時也需要用到這部分內容,那問題來了,在沒有使用消息隊列時,對于上游服務而言,需要通過擴展新的邏輯來實現該功能。同時在該場景里,會存在依賴三個下游服務,如果其中一個下游服務失敗后,該如何處理,是重試還是返回失敗等這些細節的處理。如果后期這部分數據還想在其他渠道分發,那又該如何對接。明顯這種場景下面臨著系統緊耦合的問題。
我們再來看一下,如果我們一開始就引入了消息隊列,那問題又會變成怎樣的呢?當內容審核通過后,就直接將數據生產出來丟到消息隊列中,下游的多個服務再從消息隊列消費數據。當后續這一份數據需要擴展供其他系統使用時,也只要通過新的消費者來接入到消息隊列消費就ok。上游生產消息的模塊不要做任何的改動。這樣我們就通過消息隊列進行了系統應用之間的解耦。這是消息隊列的第二個用途。
(三)業務流量削峰
消息對應的第三個使用場景便是削峰。在現如今的互聯網世界中,電商場景中每年的618秒殺活動、雙11搶購便是最典型的案例。這種場景中系統的峰值流量往往集中于一小段時間內,平常的流量比較可控,所以為了防止系統在短時間內的峰值流量沖垮,往往采用消息隊列來削弱峰值流量,高峰值期間產生的訂單消息等數據首先送入到消息隊列中暫存,然后供下游系統根據自己的消費能力來逐步處理。同時這類消息往往對時延的要求不是很高,比較適合采用消息隊列暫存。
(四)小結
最后我們在對本節的內容做一個簡單的總結,上面通過三個簡單的實例介紹了消息隊列的典型的三個使用場景:異步、解耦、削峰。換個角度來理解可以看到,消息隊列主要適用于處理對消息要求不是很實時,同時一份數據可能會多處使用的場景,不同的使用方處理速率不同。更多的消息隊列的使用場景讀者可以自行找資料閱讀和總結。
二、消息隊列“家族”有哪些成員
(一)消息隊列主流產品
上圖根據時間線展示了不同時間點產生的消息隊列產品,主要的產品有:ActiveMQ(2003)、RabbitMQ(2006)、Kafka(2010)、RocketMQ(2011)、Pulsar(2012)。這些消息隊列中或多或少我們都聽過一些,部分也在項目中真實使用過。下面對上述幾個消息隊列做一個簡單的介紹。
ActiveMQ:ActiveMQ由Apache軟件基金會基于Java語言開發的一個開源的消息代理。能夠支持多個客戶機或服務器。計算機集群等屬性支持ActiveMQ來管理通信系統。?
RabbitMQ:RabbitMQ是實現了高級消息隊列協議(AMQP)的開源消息代理軟件(亦稱面向消息的中間件)。RabbitMQ服務器是用Erlang語言編寫的,而集群和故障轉移是構建在開放電信平臺框架上的。所有主要的編程語言均有與代理接口通訊的客戶端庫。RabbitMQ支持多種消息傳遞協議、傳遞確認等特性。
Kafka:Apache Kafka是由Apache軟件基金會開發的一個開源消息系統項目,由Scala寫成。Kafka最初是由LinkedIn開發,并于2011年初開源。2012年10月從Apache Incubator畢業。該項目的目標是為處理實時數據提供一個統一、高通量、低等待的平臺。Kafka是一個分布式的、分區的、多復本的日志提交服務。它通過一種獨一無二的設計提供了一個消息系統的功能。?
RocketMQ:Apache RocketMQ是一個分布式消息和流媒體平臺,具有低延遲、強一致、高性能和可靠性、萬億級容量和靈活的可擴展性。它有借鑒Kafka的設計思想,但不是kafka的拷貝。?
Pulsar:Apache Pulsar是Apache軟件基金會頂級項目,是下一代云原生分布式消息流平臺,集消息、存儲、輕量化函數式計算為一體,采用計算與存儲分離架構設計,支持多租戶、持久化存儲、多機房跨區域數據復制,具有強一致性、高吞吐、低延時及高可擴展性等流數據存儲特性,被看作是云原生時代實時消息流傳輸、存儲和計算最佳解決方案。
(二)不同消息隊列對比
上圖詳細的展示了幾種消息隊列的各自功能及優缺點,首先,ActiveMQ和RabbitMQ二者屬于同一量級,在吞吐量上比后面三者差一個量級;其次,支持強一致性的有RocketMQ和Pulsar,在對消息一致性要求比較高的場景可以采用這些產品。同時kafka雖然會有數據丟失的風險,但其吞吐量比較高同時社區非常活躍,在大數據的絕大部分場景里,都可以采用Kafka;最后Kafka、RocketMQ、Pulsar這幾款是基于磁盤存儲數據的,內存加速訪問。而ActiveMQ、RabbitMQ采用內存存儲數據,也支持數據持久化到磁盤。
三、消息隊列背后的設計思想
在前面,第一節內容中,主要介紹了為什么要使用消息隊列,消息隊列適合解決哪些問題?在第二節內容中,又介紹了有哪些可選擇的消息隊列,以及他們之間各自的優缺點。這一節是最重要的內容,主要會介紹一下上述消息隊列背后的通用的一些設計思想。部分思想可以擴展到其他的業務模型或者領域內。后面講到對應內容也會有所提及。
(一)消息隊列核心模型
上圖是幾乎所有消息隊列設計的一個核心模型。對于一個消息隊列而言,從數據流向的維度,可以拆解為三大部分:生產者、消息隊列集群、消費者,數據是從生產者流向消息隊列集群,最終再從消息隊列集群流向消費者,下面對這幾個概念進行一一闡述。
生產者:生產數據的服務,通常也稱為數據的輸入提供方,這里的數據通常指我們的業務數據,例如推薦場景中用戶對內容的點擊數據、內容曝光數據、電商中的訂單數據等等。生產者通常是作為客戶端的方式存在,但在支持事務消息的消息隊列中,生產者也被設計為服務端,實現事務消息這一特性。其次生產者通常會有多個,消息隊列集群內部也會有多個分區隊列,所以在生產者發送數據時,通常會存在負載均衡的一些策略,常見的有按key hash、輪詢、隨機等方式。其本質是一條數據,被消息隊列封裝后也被稱為一條消息,該條消息只能發送到其消息隊列集群內部的一個分區隊列中。因此只需按照一定的策略從多個隊列中選擇一個隊列即可。
消息隊列集群:?消息隊列集群是消息隊列這種組件實現中的核心中的核心,它的主要功能是存儲消息、過濾消息、分發消息。
其中存儲消息主要指生產者生產的數據需要存儲到消息隊列內部;存儲消息可以說是消息隊列的核心,一個消息隊列吞吐量的高低、性能優劣都和它的存儲模型脫不開關系。這部分內容會在下一部分(3.2)進行介紹。?
過濾消息只指消息隊列可以通過一定的規則或者策略進行消息的過濾,該項能力通常也被稱為消息路由;過濾消息屬于高階的特性功能,AMQP協議對這些能力抽象的比較完備,部分消息隊列可以選擇性的實現該協議來達到該功能,關于AMQP協議內容讀者可以自行搜索資料閱讀,此處不再展開。
分發消息是指消息隊列通常需要將消息分發給處理同一邏輯的多個消費者處理或者處理不同邏輯的不同消費者處理。分發消息可以說和消費者模型想掛鉤,這塊會涉及到不同的數據獲取方式,也會涉及到消費者消費消息的模型。
此外絕大部分的消息隊列也都支持對消息進行分類,分類的標簽稱為topic(主題),一個topic中存放的是同一類消息。
消費者:最終消息隊列存儲的消息會被消費者消費使用,消費者也可以看做消息隊列中數據的輸出方。消費者通常有兩種方式從消息隊列中獲取數據:推送(push)數據、拉取(pull)數據。其次消費者也經常是作為客戶端的角色出現在在消息隊列這種組件中。
(二)消息隊列數據組織方式
在這一節中,我們詳細看看消息隊列存儲消息這個環節的一些權衡考量,通常數據的存儲無外乎就是兩種,一種是存儲在非易失性存儲中,例如磁盤這種介質;另一種是選擇存儲在易失性存儲中,典型的就是內存。關于二者的對比大家可以參考下表,此處就不再贅述。
通常在大部分組件設計時,往往會選擇一種主要介質來存儲、另一種介質作為輔助使用。就拿redis來說,它主要采用內存存儲數據,磁盤用來做輔助的持久化。拿RabbitMQ舉例,它也是主要采用內存存儲消息,但也支持將消息持久化到磁盤。而RocketMQ、Kafka、Pulsar這種,則是數據主要存儲在磁盤,通過內存來助力提升系統的性能。關系型數據庫例如mysql這種組件也是主要采用磁盤組織數據,合理利用內存提升性能。
針對采用內存存儲數據的方案而言,難點一方面在于如何在不降低訪問效率的情況下,充分利用有限的內存空間來存儲盡可能多的數據,這個過程中少不了對數據結構的選型、優化;另一方面在于如何保證數據盡可能少的丟失,我們可以看到針對此問題的解決方案通常是快照+廣泛意義的wal文件來解決。此類典型的代表就是redis啦。
針對采用磁盤存儲數據的方案而言,難點一方面在于如何根據系統所要解決的特點場景進行合理的對磁盤布局。讀多寫少情況下采用b+樹方式存儲數據;寫多讀少情況下采用lsm tree這類方案處理。另一方面在于如何盡可能減少對磁盤的頻繁訪問,一些做法是采用mmap進行內存映射,提升讀性能;還有一些則是采用緩存機制緩存頻繁訪問的數據。還有一些則是采用巧妙的數據結構布局,充分利用磁盤預讀特性保證系統性能。
總的來說,針對寫磁盤的優化,要不采用順序寫提升性能、要不采用異步寫磁盤提升性能(異步寫磁盤時需要結合wal保證數據的持久化,事實上wal也主要采用順序寫的特性);針對讀磁盤的優化,一方面是緩存、另一方面是mmap內存映射加速讀。
上述這些存儲方案上權衡的選擇在Kafka、RocketMQ、Pulsar中都可以看到。其實拋開消息隊列而言,這些存儲方案的選擇上無論是關系型數據庫還是kv型組件都是通用的。
下圖列舉了幾種磁盤上的數據組織方式,僅供大家參考。
(三)獲取數據的推、拉兩種方案對比
在前面3.1節中提到,消費者在從消息隊列中獲取數據時,主要有兩種方案:
等待推送數據
主動拉取數據
目前的消息隊列實現時,都會選擇支持兩種的至少一種,關于這兩種方案的對比,大家可以參考下表。
在此處,個人想拋開消息隊列談一點關于這兩種方案的理解,其實推拉模型不僅僅只用于消息隊列這種組件中,更一般意義上,它解決的其實是數據傳送雙方的一個問題。本質是數據需要從一方流向另一方。順著這個思路來看,下面這三個例子都是遵循這個原則。
網絡中傳輸的數據:在IO多路復用中,以epoll為例,當內核檢測到監聽的描述符有數據到來時,epoll_wait()阻塞會返回,上層用戶態程序就知道有數據就緒了,然后可以通過read()系統調用函數讀取數據。這個過程就緒通知,類似于推送,但推送的不是數據,而是數據就緒的信號。具體的數據獲取方式還是需要通過拉取的方式來主動讀。
feeds流系統用戶時間線后臺實現方案(讀擴散、寫擴散):?讀擴散和寫擴散更是這樣一個case。對于讀擴散而言,主要采用拉取的方式獲取數據。而對于寫擴散而言,它是典型的數據推送的方式。當然在系統實現中,更復雜的場景往往會選擇讀寫結合的思路來實現。
生活中的點外賣例子:當下單點外賣時,通常也會有兩種方式可以選擇:外賣派送、到店自取。不過通常外賣派送比較實時,我們通常就選擇這種方式了而已。可以看出外賣派送其實就是一種推的方式,而到店自取,則是拉的方式。
(四)消息隊列消費模型
本節我們來介紹最后一部分內容,消息隊列中消費者的消費模型。下圖中上半部分展示了最簡單的一種消費模型。一個生產者、一個消費者。但往往我們的一份數據通常會被不同場景所使用。那這個時候,首先就會存在每種場景需要使用全量的數據、而且不同場景之間不會相關影響,彼此獨立。方便理解起見,我們假設有N個場景需要使用這同一份數據,每個場景需要消費全量的數據。而在N個場景中的一種場景里,又會有多個消費者一起分攤消費這些數據。我們假設一個場景里有M個消費者。由于每個場景中包含M個消費者,我們也將其采用消費者組來描述。通過上面的介紹,我們可以用下面一句話總結消息隊列中的消費模型:
消費者消費者模型其實是一個1:N:M的關系,一份數據被N個消費者組獨立使用,每個消費者組中有M個消費者進行分攤消費。
其實這種模型也稱為發布訂閱模型,對于一條消息而言,組間廣播、組內單播。一條消息只能被一個消費者組中的一個消費者使用。在消費者組內部也存在一些負載均衡的策略。常用的有:輪詢、隨機、hash、一致性hash等方案。
(五)小結
第三部分內容我們重點介紹了關于消息隊列背后的一些設計思想,其中包括:消息隊列的核心模型、數據存儲模型、推拉方案獲取數據對比、消費者消費模型。其中數據存儲模型不僅僅適用于消息隊列,也同樣適用于其他數據存儲組件的方案選擇。同理數據獲取的推拉兩種方案也不僅僅局限于消息隊列。我們可以在很多業務場景里看到同類思想的影子。
四、總結
到此,本文也就告一段落了。本文主要從理論、抽象層面泛泛的談了下關于消息隊列的一些思想和理念。主要介紹了消息隊列的使用場景,主流的消息隊列可選方案以及他們之間的優缺點。最后介紹了一些關于消息隊列背后的設計理念。本文只是拋磚引玉,希望上述內容能輔助大家一起重新認識消息隊列。后面會逐步挑選上述的幾種消息隊列(kafka、RocketMQ、Pulsar),重點分析其內部實現機制,敬請期待。限于個人水平有限,理解有誤之處歡迎大家批評指正。
參考資料
1. ActiveMQ與RabbitMQ的區別
2. Kafka、ActiveMQ、RabbitMQ、RocketMQ?區別以及高可用原理
3. Kafka、RabbitMQ、RocketMQ等消息中間件的對比
4. Apache?Pulsar?在騰訊計費場景下的應用
5. kafka?Push?vs.?pull
6. 消息隊列-推/拉模式學習?&?ActiveMQ及JMS學習
- END -
看完一鍵三連在看,轉發,點贊
是對文章最大的贊賞,極客重生感謝你
推薦閱讀
深入理解Kafka的設計思想
經典算法刷題筆記pdf
編程究竟難在哪?
Redis 多線程網絡模型全面揭秘|網絡硬核系列
總結
以上是生活随笔為你收集整理的深入理解消息队列(场景,对比,原理和设计思想)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 实战能力|一文看懂GDB调试底层实现
- 下一篇: 深入理解操作系统内核架构(送书)!