MQ 系列 2:消息中間件的技術選型
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 消息中間件常用協議
AMQP 協議、MQTT 協議、STOMP 協議、XMPP 協議、其他基於 TCP/IP 自定義的協議。
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 對比
6.3 選型建議
6.3.1 系統建設規模角度
中小型系統建議選用 RabbitMQ,數據量相對較小,選型應首選功能比較完備的,所以 kafka 排除。RocketMQ 是阿里出品,如果阿里放棄維護,中小型公司一般很難投入人力進行 RocketMQ 的定製化開發,因此不推薦。
6.3.2 業務規模角度
根據具體使用規模在 RocketMQ 和 kafka 之間二選一。
大型業務系統:有實際的業務體量需求,比如足夠大規模的分佈式環境,以及足夠大的數據量。這時候 RocketMQ 和 kafka 都是 10w + 的吞吐量,都可以在考慮範圍內。
如果你有業務定製需求,可以優先選用 RocketMQ,畢竟是開源的,大的業務系統也願意花精力去優化 JAVA 源碼的。至於 kafka,根據業務方向選擇,類似日誌採集功能,首選 kafka,因爲他在日誌上報、監控數據採集方面有着大規模的實踐經驗,這也是他們主打的應用場景。
具體該選哪個,看使用場景。引入 MQ 之後,也會有一定的弊端,必然一定程度上降低系統可用性,增加複雜性。
6.3.3 功能性層面選型
-
優先級隊列:可配置優先級,優先級高的消息具備優先被消費的特權,這樣可以爲下游服務提供不同消息級別的保證。這種模式只是在生產效率高於消費效率的時候纔有效果。如果消費者的消費速度大於生產者的速度,消息中間件服務器(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 來衡量。
對應的 RabbitMQ 是通過鏡像環形隊列實現多副本及強一致性語義的。多副本可以保證在 master 節點宕機異常之後可以提升 slave 作爲新的 master 而繼續提供服務來保障可用性。
Kafka 設計之初是爲日誌處理而生,給人們留下了數據可靠性要求不高的不良印象,但是隨着版本的升級優化,其可靠性得到極大的增強,詳細可以參考 KIP101。
目前情況,在金融支付領域使用 RabbitMQ 居多,而在日誌處理、大數據等方面 Kafka 使用居多。
6.3.6 運維管理層面
消息中間件一個很重要的考慮層面是運維管理,比如:申請、審覈、監控、告警、管理、容災、部署等。
對消息中間件的使用 從使用、接入規範、全方位的監控、流量統計和分析等方面,提供有效的基準數據,也可以在檢測到異常的情況配合告警,以便運維、開發人員的迅速介入。除了一般的監控項(比如硬件、GC 等)之外,對於消息中間件還需要關注端到端時延、消息審計、消息堆積等方面。
對於 RabbitMQ 而言,最正統的監控管理工具莫過於 rabbitmq_management 插件了,但是社區內還有 AppDynamics, Collectd, DataDog, Ganglia, Munin, Nagios, New Relic, Prometheus, Zenoss 等多種優秀的產品。
Kafka 在此方面也毫不遜色,比如:Kafka Manager, Kafka Monitor, Kafka Offset Monitor, Burrow, Chaperone, Confluent Control Center 等產品,尤其是 Cruise 還可以提供自動化運維的功能。
機房故障就要涉及異地容災了,關鍵點在於如何有效的進行數據複製,對於 Kafka 而言,可以參考 MirrorMarker、uReplicator 等產品,而 RabbitMQ 可以參考 Federation 和 Shovel。
6.3.7 社區力度及生態發展
Kafka 和 RabbitMQ 都有一系列開源的監控管理產品,這些都得益於其社區及生態的迅猛發展。Rocket MQ 基於阿里的推廣,整個生態也是不錯的。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/4PSgVlO5s0ViKfpsWMusag