1 背景
在高併發、高訊息吞吐的網際網路場景中,我們經常會使用訊息佇列(Message Queue)作為基礎設施,在服務端架構中擔當訊息中轉、訊息削峰、事務非同步處理 等職能。
對於那些不需要實時響應的的業務,我們都可以放在訊息佇列中進行傳輸。下面是使用者在進行系統註冊的時候場景,充分體現MQ的作用
可以看到使用者註冊的過程步驟1+步驟2,從請求到響應總共耗時 55 ms。訊息消費+簡訊傳送的時間比較長,從上面看花了5s多,一般讓訊息佇列服務去處理,使用者靜靜等待簡訊送達即可。
訊息佇列中介軟體(簡稱訊息中介軟體)是指利用高效可靠的訊息傳遞機制進行與平臺無關的資料交流,並基於資料通訊來進行分散式系統的整合。通過提供訊息傳遞和訊息排隊模型,它可以在分散式環境下提供應用解耦、
彈性伸縮、冗餘儲存、流量削峰、非同步通訊、資料同步等等功能,其作為分散式系統架構中的一個重要元件,有著舉足輕重的地位。
2 訊息中介軟體的組成
Broker:訊息伺服器,以服務的形式執行在server端,給各個業務系統提供核心訊息資料的中轉服務。
Producer:訊息生產者,業務的發起方,負責生產訊息傳輸給broker。
Consumer:訊息消費者,業務的處理方,負責從broker獲取訊息並進行業務邏輯處理
Topic:主題模組,釋出/訂閱模式下的訊息統一彙集地,不同生產者向topic傳送訊息,由MQ伺服器分發到不同的訂閱者,實現訊息的廣播
Queue:佇列,PTP模式下,特定生產者向特定queue傳送訊息,消費者訂閱特定的queue完成指定訊息的接收。
Message:訊息體,根據不同通訊協議定義的固定格式進行編碼的資料包,來封裝業務資料,實現訊息的傳輸。
這邊以kafka為例子,這是典型的叢集模式,Kafka通過Zookeeper管理叢集配置,選舉leader,以及在Consumer Group發生變化時進行rebalance。Producer使用push模式將訊息釋出到broker,Consumer使用pull模式從broker訂閱並消費訊息。
- producer 負責生產訊息
- consumer 負責消費訊息
- broker 訊息伺服器,提供訊息核心的處理工作
- zookeeper 用於生產者和消費者的註冊與發現
3 訊息中介軟體的模式分類
PTP點對點:使用queue作為通訊載體
訊息生產者生產訊息傳送到queue中,然後訊息消費者從queue中取出並且消費訊息。
不可重複消費,訊息被消費以後,queue中不再儲存,所以訊息消費者不可能消費到已經被消費的訊息。 Queue支援存在多個消費者,但是對一個訊息而言,只會有一個消費者可以消費。
Pub/Sub釋出訂閱(廣播):使用topic作為通訊載體
訊息生產者(釋出)將訊息釋出到topic中,同時有多個訊息消費者(訂閱)消費該訊息。和點對點方式不同,釋出到topic的訊息會被所有訂閱者消費,所以從1到N個訂閱者都能得到這個訊息的拷貝。
4 訊息中介軟體的優勢
系統解耦:互動系統之間沒有直接的呼叫關係,只是通過訊息傳輸,故系統侵入性不強,耦合度低。
削峰、提高系統響應時間:例如原來的一套邏輯,可將緊急重要(需要立刻響應)的業務放到該呼叫方法中,響應要求不高的使用訊息佇列,放到MQ佇列中,供消費者處理。
業務的有序性處理:先來先處理,比如一個系統處理某件事需要很長一段時間,但是在處理這件事情時候,有其他人也發出了請求,可以把請求放在訊息隊裡,一個一個來處理
為大資料處理架構提供服務:通過訊息作為整合,大資料的背景下,訊息佇列還與實時處理架構整合,為資料處理提供效能支援。
5 訊息中介軟體常用協議
6 豐富強大的訊息中介軟體生態
目前開源的訊息中間還是很豐富的,大家用的比較多的比如 ActiveMQ、RabbitMQ、Kafka、RocketMQ、ZeroMQ 等。
但是每個人的業務場景不一樣,受限於系統的規模,業務的取捨(如延遲容忍度,死信、重試的需求,可持久化需求),並不是每一款訊息中介軟體都能滿足你的需求。
除了個別大廠會進行自研(如 阿里的Rocket MQ、滴滴的DD MQ)之外,大部分同學還是要對選型有一些思考的。各自都有各自的側重點,選擇合適自己、揚長避短無疑是最好的方式。
6.1 主流MQ介紹
下面基於受眾程度,對三款主流的MQ做介紹,通過各項指標上的對比,給出我們在實際應用場景中的建議。
RabbitMQ:
採用 Erlang 語言實現的 AMQP 協議的訊息中介軟體,起源於金融系統,廣泛應用在分散式系統中,承擔訊息轉發的職責。RabbitMQ 發展歷史比較久遠,影響範圍比較大,被很多開發者認可,在可靠性、可用性、可擴充套件性、功能性方面有著非凡表現。
RocketMQ:
阿里開源的訊息中介軟體,目前已經捐獻給 Apache 基金會,它是由 Java 語言開發的,具備高吞吐量、高可用性、適合大規模分散式系統應用等特點。並且在阿里的雙11、618等重要活動中經受住了考驗。
Kafka:
起初是由 LinkedIn 公司採用 Scala 語言開發的一個分散式、多分割槽、多副本且基於 zookeeper 協調的分散式訊息系統,現已捐獻給 Apache 基金會。它是一種高吞吐量的分散式釋出訂閱訊息系統,以可水平擴充套件和高吞吐率而被廣泛使用。
目前越來越多的開源分散式處理系統如 Cloudera、Apache Storm、Spark、Flink 等都支援與 Kafka 整合。
6.2 主流MQ對比
特性
|
RabbitMQ
|
RocketMQ
|
kafka
|
開發語言
|
erlang
|
java
|
scala
|
支援協議
|
AMQP
|
自定義
|
基於TCP 自定義
|
訊息儲存能力
|
記憶體、磁碟。支援少量堆積。
|
磁碟。支援大量堆積。
|
記憶體、磁碟、資料庫。支援大量堆積。
|
訊息事務性
|
支援(通道設定事務模式,效能有影響)
|
支援
|
支援
|
單機吞吐量
|
萬級
|
10萬級+
|
10萬級+
|
時效性
|
us級
|
ms級
|
ms級以內
|
訊息重複
|
支援at least once、at most once |
支援at least once
|
支援at least once、at most once |
訊息回溯
|
不支援
|
支援指定時間點的回溯
|
支援指定分割槽offset位置的回溯
|
訊息重試
|
不支援,但可以設定autoACK=false,未收到確認的會重入佇列
|
支援
|
不支援,但可以通過訊息回溯的方式來實現
|
可用性
|
高(主從架構)
|
非常高(分散式架構)
|
非常高(分散式架構)
|
功能特性說明
|
基於erlang開發,所以併發能力很強,效能極其好,延時很低;
管理介面較豐富
|
MQ功能比較完備,擴充套件性佳
|
只支援主要的MQ功能,像一些訊息查詢,訊息回溯等功能支援的不是很強,在大資料領域應用廣。
|
6.3 選型建議
6.3.1 系統建設規模角度
中小型系統建議選用RabbitMQ,資料量相對較小,選型應首選功能比較完備的,所以kafka排除。RocketMQ是阿里出品,如果阿里放棄維護,中小型公司一般很難投入人力進行RocketMQ的定製化開發,因此不推薦。
6.3.2 業務規模角度
6.3.3 功能性層面選型
功能項 | Kafka(1.1.0+) | RabbitMQ(3.6.10+) |
優先順序佇列 | 不支援 | 支援:具有優先被消費的特權,建議優先順序大小設定在10以內,否則價值不大 |
延遲佇列 | 不支援 | 支援 |
死信佇列 | 不支援 | 支援:儲存無法被正確投遞的訊息,避免訊息被無端丟棄。 |
重試模式 | 不支援 |
不支援:RabbitMQ中可以參考延遲佇列實現一個重試佇列,需要再封裝一下,也不是太難。 如果要在kafka中實現重試佇列,首先要實現延遲佇列的功能,相對比較複雜。 |
消費模式 | 拉 模式 | 推+拉 模式 |
廣播消費(pub/sub) | 支援:kafka對廣播消費的支援比較強大 | 支援:能力相比較kafka 弱一些 |
訊息回溯 | 支援:kafka可以按照 offset(偏移量)和 timestamp(時間戳) 兩種維度進行訊息回溯。 | 不支援:RabbitMQ訊息一旦被確認消費便丟棄 |
訊息堆積 | 支援 |
支援:記憶體堆積過大會影響效能,如果僅考慮吞吐量因素,kafka的堆積效率比RabbitMQ總體高很多。 |
持久化 | 支援 | 支援 |
訊息追蹤 | 不支援:訊息追蹤可以通過外部系統來支援,但是支援粒度肯定沒有內建的細膩 |
支援:RabbitMQ中可以採用Firehose 或者 rabbitmq_tracing外掛實現。
|
訊息過濾 | 客戶端級別的支援 | 不支援,可以定製化封裝 |
多租戶 | 不支援 | 支援 |
多協議支援 | 只支援自定義協議,目前幾個主流版本中存在相容性問題。 | RabbitMQ本身就是AMQP協議的實現,同時支援MQTT、STOMP等協議 |
跨語言支援 | 採用Scala和Java編寫,支援多種語言的客戶端 | 採用Erlang編寫,支援多種語言的客戶端 |
流量控制 | 支援client和user級別,可將流控配置在生產者和消費者層面 | RabbitMQ的流控基於 Credit-Based 演算法,是內部被動觸發的保護機制,僅用於生產者層面。 |
訊息順序性 | 支援單分割槽(partition)級別的順序性,在各自的分割槽中排序 |
順序性消費的條件比較苛刻,需要單執行緒傳送、單執行緒消費,這樣吞吐量就下來了。 而且無法使用延遲佇列、優先佇列等一些高階功能,所以一般不使用。 |
安全機制 |
(TLS/SSL、SASL)身份認證和(讀/寫)許可權控制 |
與kafka相似 |
冪等性 | 單個生產者+單partition + 單會話 場景下,支援冪等性 | 不支援 |
事務性訊息 | 支援 | 支援 |
- 優先順序佇列:可配置優先順序,優先順序高的訊息具備優先被消費的特權,這樣可以為下游服務提供不同訊息級別的保證。這種模式只是在生產效率高於消費效率的時候才有效果。如果消費者的消費速度大於生產者的速度,訊息中介軟體伺服器(Broker)中沒有訊息堆積,就不存在對待消費資料進行優先順序排序的需求了。
- 延遲佇列:延遲佇列會儲存對應的延遲訊息,延遲訊息是指訊息被生產後,並不馬上消費,而是等待一定時間後,消費者才拿到訊息進行消費。延遲佇列的模式分為兩種,基於訊息的延遲和基於佇列的延遲。
- 基於訊息的延遲是指為每條訊息設定不同的延遲時間,那麼每當佇列中有新訊息進入的時候就會重新根據延遲時間排序,但是這會對效能造成很大的影響。
- 基於佇列的延遲,設定不同延遲級別的佇列,如 15s、30s、1m、10m 等,每個佇列中訊息的延遲時間都是相同的,這樣不需要消耗大量效能去做延遲時間排序,每個訊息都有固定的投遞時間。
- 延遲佇列的常用的場景有以下幾種:
- 1、購買火車票提示:30分鐘之內未付款,將自動取消訂單!
- 2、雙11網購時,距離聚划算活動開始時間還有 17小時,到時全場5折優惠。
- 死信佇列:由於某些原因訊息無法被正確的投遞,為了確保訊息不會被無故的丟棄,一般會儲存到一個特殊的佇列中,我們稱之為死信佇列。與此對應的還有一個“回退佇列”的概念,試想如果消費者在消費時發生了異常,那麼就不會對這一次消費進行確認(Ack), 進而發生回滾訊息的操作之後訊息始終會放在佇列的頂部,然後不斷被處理和回滾,導致佇列陷入死迴圈。為了解決這個問題,可以為每個佇列設定一個回退佇列,它和死信佇列都是為異常的處理提供的一種機制保障。實際情況下,回退佇列的角色可以由死信佇列和重試佇列來扮演。
- 重試佇列:重試佇列其實可以看成是一種回退佇列,具體指消費端消費訊息失敗時,為防止訊息無故丟失而重新將訊息回滾到 Broker 中。與回退佇列不同的是重試佇列一般分成多個重試等級,每個重試等級一般也會設定重新投遞延時,重試次數越多投遞延時就越大。比如第一次重試延遲時間為5s,再次消費失敗後延遲重試時間為10s,以此類推,重試越多次重新投遞的時間就越久。為了避免延遲時間被無限放大,需要有個重試次數限制,超過就寫入死信佇列。這邊需要注意:延遲佇列動作由內部觸發,重試佇列動作由外部消費端觸發。
- 消費模式:消費模式分為推(push)模式和拉(pull)模式。推模式是指由 Broker 主動推送訊息至消費端,實時性較好,不過需要保證服務端推送的訊息不會嚴重超過消費端消化能力。而拉模式是指消費端定時定量主動向 Broker 端請求拉取訊息,雖然實時性較差,但是可以根據自身的消費能力來拉取。
- 廣播消費:訊息一般有兩種傳送模式:點對點(P2P,Point-to-Point)模式和釋出/訂閱(Pub/Sub)模式。對於P2P模式而言,訊息被消費以後,佇列中不會再儲存,即使有多個消費者,一條訊息只會被一個消費者消費。而釋出訂閱(Pub/Sub)模式定義瞭如何向一個內容節點發布和訂閱訊息,這個內容節點稱為主題(topic),主題可以認為是訊息傳遞的中介,訊息釋出者將訊息釋出到某個主題,而訊息訂閱者則從主題中訂閱訊息。主題使得訊息的訂閱者與訊息的釋出者互相保持獨立,不需要進行接觸即可保證訊息的傳遞,釋出 / 訂閱模式在訊息的一對多廣播時採用。RabbitMQ 是一種典型的點對點模式,而 Kafka 是一種典型的釋出訂閱模式。
- 訊息回溯:一般訊息在消費完成之後就被處理了,之後再也不能消費到該條訊息。訊息回溯正好相反,是指訊息在消費完成之後,還能追溯到之前被消費掉的訊息。
- 訊息堆積 + 持久化:進行流量的削峰填谷是訊息中介軟體的一個核心功能,實現的能力主要體現在訊息堆積能力上。訊息堆積分記憶體式堆積和磁碟式堆積。RabbitMQ 是典型的記憶體式堆積,可以通過一些方式持久化到磁碟中,但是會降低一些效能。Kafka 是典型的磁碟式堆積,所有的訊息都儲存在磁碟中,儲存容量是有了很大的提升,但是磁碟效能會比記憶體差很多。
- 訊息追蹤:在訊息中介軟體中,訊息的鏈路追蹤非常重要,它可以對生產和消費過的訊息進行trace追蹤。這樣,在出現故障的時候,就可以快速的定位問題。
- 訊息過濾:訊息過濾是指按照既定的過濾規則為下游使用者提供指定類別的訊息。就以 kafka 而言,完全可以將不同類別的訊息傳送至不同的 topic 中,由此可以實現某種意義的訊息過濾,或者 Kafka 還可以根據分割槽對同一個 topic 中的訊息進行分類。不過更加嚴格意義上的訊息過濾應該是對既定的訊息採取一定的方式按照一定的過濾規則進行過濾。同樣以 Kafka 為例,可以通過客戶端提供的 ConsumerInterceptor 介面或者 Kafka Stream 的 filter 功能進行訊息過濾。
- 流量控制:flow control,當生產者和消費者 處理速度不均衡問題,通過對生產者和消費者的限流,來保障兩者的均衡。通常的流控方法有 Stop-and-wait、滑動視窗以及令牌桶等。
- 訊息順序性:順序性是指保證訊息有序,特別是分散式場景下,有序的執行,是保證一致性 (Consistency)的前提。
- 訊息冪等性:對於確保訊息在生產者和消費者之間進行傳輸而言一般有三種傳輸保障(delivery guarantee):At most once,至多一次,訊息可能丟失,但絕不會重複傳輸;At least once,至少一次,訊息絕不會丟,但是可能會重複;Exactly once,精確一次,每條訊息肯定會被傳輸一次且僅一次。對於大多數訊息中介軟體而言,一般只提供 At most once 和 At least once 兩種傳輸保障,對於第三種一般很難做到,由此訊息冪等性也很難保證。
- 事務性訊息:原子性事務中的操作為一個整體,要麼都做,要麼都不做。即一旦出錯,就回滾事務,事務是由事務開始(Begin Transaction)和事務結束(End Transaction)之間執行的全體操作組成。Kafka 和 RabbitMQ 都支援,不過僅僅指的是生產者傳送訊息是一個事務性操作,要麼傳送成功,要麼傳送失敗。
6.3.4 效能層面
功能維度是訊息中介軟體選型中的一個重要的參考維度,但效能也是考慮的一個重要環節。
吞吐量角度:Kafka 在開啟冪等、事務功能的時候會使其效能降低,RabbitMQ 在開啟 rabbitmq_tracing 外掛的時候也會極大的影響其效能。訊息中介軟體的效能一般是指其吞吐量,雖然從功能維度上來說,RabbitMQ 的優勢要大於 Kafka,但是 Kafka 的吞吐量要比 RabbitMQ 高出 1 至 2 個數量級,一般 RabbitMQ 的單機 QPS 在萬級別之內,而 Kafka 的單機 QPS 可以維持在十萬級別,甚至可以達到百萬級。
時延角度:另外一個是時延,作為效能維度的一個重要指標,卻往往在訊息中介軟體領域所被忽視,因為一般使用訊息中介軟體的場景對時效性的要求並不是很高,如果要求時效性完全可以採用 RPC 的方式實現。訊息中介軟體具備訊息堆積的能力。Kafka是ms以內,RabbitMQ是us級別的。
6.3.5 高可用角度
高可用角度是指系統的出錯概率和無故障執行時長。
如訊息丟失,是使用訊息中介軟體時所不得不面對的一個同點,其背後訊息可靠性也是衡量訊息中介軟體好壞的一個關鍵因素。尤其是在金融支付領域,訊息可靠性尤為重要。然而說到可靠性必然要說到可用性,注意這兩者之間的區別,訊息中介軟體的可靠性是指對訊息不丟失的保障程度;
而訊息中介軟體的可用性是指無故障執行的時間百分比,通常用幾個 9 來衡量,如 99.99% 就是一個不錯的指標。
對應的 RabbitMQ 是通過映象環形佇列實現多副本及強一致性語義的。多副本可以保證在 master 節點當機異常之後可以提升 slave 作為新的 master 而繼續提供服務來保障可用性。
6.3.6 運維管理層面
訊息中介軟體一個很重要的考慮層面是運維管理,比如:申請、稽核、監控、告警、管理、容災、部署等。
對訊息中介軟體的使用 從使用、接入規範、全方位的監控、流量統計和分析等方面,提供有效的基準資料,也可以在檢測到異常的情況配合告警,以便運維、開發人員的迅速介入。除了一般的監控項(比如硬體、GC 等)之外,對於訊息中介軟體還需要關注端到端時延、訊息審計、訊息堆積等方面。
對於 RabbitMQ 而言,最正統的監控管理工具莫過於 rabbitmq_management 外掛了,另外還有 AppDynamics, Collectd, DataDog, Ganglia 等多種優秀的產品。
Kafka 豐富的管理工具,比如:Kafka Manager, Kafka Monitor, Kafka Offset Monitor 等產品,其中 Cruise 還可以提供自動化運維的功能。
6.3.7 社群力度及生態發展
Kafka 和 RabbitMQ 都有一系列開源的監控管理產品,社群活躍,產品生態都很不錯。