消息中間件這么多,到底應該如何選型?
消息隊列已經逐漸成為企業應用系統內部通信的核心手段。它具有低耦合、可靠投遞、廣播、流量控制、最終一致性等一系列功能。
圖片來自 Pexels
當前使用較多的消息隊列有 RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMQ 等,而部分數據庫如 Redis、MySQL 以及 PhxSQL 也可實現消息隊列的功能。
消息隊列概述
消息隊列是指利用高效可靠的消息傳遞機制進行與平臺無關的數據交流,并基于數據通信來進行分布式系統的集成。
通過提供消息傳遞和消息排隊模型,它可以在分布式環境下提供應用解耦、彈性伸縮、冗余存儲、流量削峰、異步通信、數據同步等等功能,其作為分布式系統架構中的一個重要組件,有著舉足輕重的地位。
消息隊列的特點
采用異步處理模式
消息發送者可以發送一個消息而無須等待響應。消息發送者將消息發送到一條虛擬的通道(主題或隊列)上,消息接收者則訂閱或是監聽該通道。
一條信息可能最終轉發給一個或多個消息接收者,這些接收者都無需對消息發送者做出同步回應。整個過程都是異步的。
應用系統之間解耦合
主要體現在如下兩點:
- 發送者和接受者不必了解對方、只需要確認消息。
- 發送者和接受者不必同時在線。
比如在線交易系統為了保證數據的最終一致,在支付系統處理完成后會把支付結果放到消息中間件里,通知訂單系統修改訂單支付狀態。兩個系統是通過消息中間件解耦的。
消息隊列的傳遞服務模型
消息隊列的傳遞服務模型如下圖所示:
消息隊列的的傳輸模式
點對點模型
點對點模型用于消息生產者和消息消費者之間點到點的通信。消息生產者將消息發送到由某個名字標識的特定消費者。
這個名字實際上對于消費服務中的一個 隊列( Queue),在消息傳遞給消費者之前它被存儲在這個隊列中。
隊列消息可以放在內存中也可以持久化,以保證在消息服務出現故障時仍然能夠傳遞消息。
傳統的點對點消息中間件通常由消息隊列服務、消息傳遞服務、消息隊列和消息應用程序接口 API 組成。
其典型的結構如下圖所示:
特點如下:
- 每個消息只用一個消費者。
- 發送者和接受者沒有時間依賴。
- 接受者確認消息接受和處理成功。
示意圖如下所示:
發布/訂閱模型(Pub/Sub)
發布者/訂閱者模型支持向一個特定的消息主題生產消息。0 或多個訂閱者可能對接收來自特定消息主題的消息感興趣。
在這種模型下,發布者和訂閱者彼此不知道對方,就好比是匿名公告板。這種模式被概括為:多個消費者可以獲得消息,在發布者和訂閱者之間存在時間依賴性。
發布者需要建立一個訂閱( Subscription),以便消費者能夠訂閱。訂閱者必須保持持續的活動狀態并接收消息。
在這種情況下,在訂閱者未連接時,發布的消息將在訂閱者重新連接時重新發布,如下圖所示:
特性如下:
- 每個消息可以有多個訂閱者。
- 客戶端只有訂閱后才能接收到消息。
- 持久訂閱和非持久訂閱。
注意以下三點:
- 發布者和訂閱者有時間依賴:接受者和發布者只有建立訂閱關系才能收到消息。
- 持久訂閱:訂閱關系建立后,消息就不會消失,不管訂閱者是否都在線。
- 非持久訂閱:訂閱者為了接受消息,必須一直在線。當只有一個訂閱者時約等于點對點模式。
消息隊列應用場景
當你需要使用消息隊列時,首先需要考慮它的必要性。可以使用消息隊列的場景有很多,最常用的幾種,是做應用程序松耦合、異步處理模式、發布與訂閱、最終一致性、錯峰流控和日志緩沖等。
反之,如果需要強一致性,關注業務邏輯的處理結果,則使用 RPC 顯得更為合適。
異步處理
非核心流程異步化,減少系統響應時間,提高吞吐量。例如:短信通知、終端狀態推送、App 推送、用戶注冊等。
消息隊列 一般都內置了高效的通信機制,因此也可以用于單純的消息通訊,比如實現點對點消息隊列或者聊天室等。
應用案例:網站用戶注冊,注冊成功后會過一會發送郵件確認或者短信。
系統解耦
系統之間不是強耦合的,消息接受者可以隨意增加,而不需要修改消息發送者的代碼。
消息發送者的成功不依賴消息接受者(比如:有些銀行接口不穩定,但調用方并不需要依賴這些接口)。
不強依賴于非本系統的核心流程,對于非核心流程,可以放到消息隊列中讓消息消費者去按需消費,而不影響核心主流程。
最終一致性
最終一致性不是消息隊列的必備特性,但確實可以依靠消息隊列來做最終一致性的事情:
先寫消息再操作,確保操作完成后再修改消息狀態。定時任務補償機制實現消息可靠發送接收、業務操作的可靠執行,要注意消息重復與冪等設計。
所有不保證 100% 不丟消息的消息隊列,理論上無法實現最終一致性。
像 Kafka 一類的設計,在設計層面上就有丟消息的可能(比如定時刷盤,如果掉電就會丟消息)。哪怕只丟千分之一的消息,業務也必須用其他的手段來保證結果正確。
廣播
生產者/消費者模式,只需要關心消息是否送達隊列,至于誰希望訂閱和需要消費,是下游的事情,無疑極大地減少了開發和聯調的工作量。
流量削峰和流控
當上下游系統處理能力存在差距的時候,利用消息隊列做一個通用的 “漏斗”,進行限流控制。在下游有能力處理的時候,再進行分發。
舉個例子:用戶在支付系統成功結賬后,訂單系統會通過短信系統向用戶推送扣費通知。
短信系統可能由于短板效應,速度卡在網關上(每秒幾百次請求),跟前端的并發量不是一個數量級。于是,就造成支付系統和短信系統的處理能力出現差異化。
然而用戶晚上個半分鐘左右收到短信,一般是不會有太大問題的。如果沒有消息隊列,兩個系統之間通過協商、滑動窗口等復雜的方案也不是說不能實現。
但系統復雜性指數級增長,勢必在上游或者下游做存儲,并且要處理定時、擁塞等一系列問題。
而且每當有處理能力有差距的時候,都需要單獨開發一套邏輯來維護這套邏輯。
所以,利用中間系統轉儲兩個系統的通信內容,并在下游系統有能力處理這些消息的時候,再處理這些消息,是一套相對較通用的方式。
應用案例:
- 把消息隊列當成可靠的消息暫存地,進行一定程度的消息堆積。
- 定時進行消息投遞,比如模擬用戶秒殺訪問,進行系統性能壓測。
日志處理
將消息隊列用在日志處理中,比如 Kafka 的應用,解決海量日志傳輸和緩沖的問題。
應用案例:把日志進行集中收集,用于計算 PV、用戶行為分析等等。
消息通訊
消息隊列一般都內置了高效的通信機制,因此也可以用于單純的消息通訊,比如實現點對點消息隊列或者聊天室等。
消息隊列的推拉模型
Push 推消息模型
消息生產者將消息發送給消息隊列,消息隊列又將消息推給消息消費者。
Pull 拉消息模型
消費者請求消息隊列接受消息,消息生產者從消息隊列中拉該消息。
兩種類型的區別
兩種類型的區別如下圖:
消息隊列技術對比
本部分主要介紹四種常用的消息隊列( ActiveMQ/RabbitMQ/RocketMQ/Kafka)的主要特性、優點、缺點。
ActiveMQ
ActiveMQ 是由 Apache 出品, ActiveMQ 是一個完全支持 JMS1.1 和 J2EE1.4 規范的 JMS Provider 實現。
它非常快速,支持多種語言的客戶端和協議,而且可以非常容易的嵌入到企業的應用環境中,并有許多高級功能。
主要特性:
- 服從 JMS 規范:JMS 規范提供了良好的標準和保證,包括:同步或異步的消息分發,一次和僅一次消息分發,消息接收和訂閱等等。
- 遵從 JMS 規范的好處在于,不論使用什么 JMS 實現提供者,這些基礎特性都是可用的。
- 連接靈活性:ActiveMQ 提供了廣泛的連接協議,支持的協議有:HTTP/S,IP 多播,SSL,TCP,UDP 等等。對眾多協議的支持讓 ActiveMQ 擁有了很好的靈活性。
- 支持的協議種類多:OpenWire、STOMP、REST、XMPP、AMQP。
- 持久化插件和安全插件:ActiveMQ 提供了多種持久化選擇。而且, ActiveMQ 的安全性也可以完全依據用戶需求進行自定義鑒權和授權。
- 支持的客戶端語言種類多:除了 Java 之外,還有 C/C++,.Net,Perl, PHP,Python,Ruby。
- 代理集群:多個 ActiveMQ 代理可以組成一個集群來提供服務。
- 異常簡單的管理:ActiveMQ 是以開發者思維被設計的。所以,它并不需要專門的管理員,因為它提供了簡單又實用的管理特性。
有很多種方法可以監控 ActiveMQ 不同層面的數據,包括使用在 JConsole 或者在 ActiveMQ 的 WebConsole 中使用 JMX。
通過處理 JMX 的告警消息,通過使用命令行腳本,甚至可以通過監控各種類型的日志。
部署環境:ActiveMQ 可以運行在 Java 語言所支持的平臺之上。
使用 ActiveMQ 需要:
- JavaJDK
- ActiveMQ 安裝包
優點如下:
- 跨平臺(Java 編寫與平臺無關,ActiveMQ 幾乎可以運行在任何的 JVM上)。
- 可以用 JDBC:可以將數據持久化到數據庫。雖然使用 JDBC 會降低 ActiveMQ 的性能,但是數據庫一直都是開發人員最熟悉的存儲介質。
- 支持 JMS 規范:支持 JMS 規范提供的統一接口。
- 支持自動重連和錯誤重試機制。
- 有安全機制:支持基于 Shiro,JAAS 等多種安全配置機制,可以對 Queue/Topic 進行認證和授權。
- 監控完善:擁有完善的監控,包括 WebConsole,JMX,Shell 命令行, Jolokia 的 RESTfulAPI。
- 界面友善:提供的 WebConsole 可以滿足大部分情況,還有很多第三方的組件可以使用,比如 Hawtio。
缺點如下:
- 社區活躍度不及 RabbitMQ 高。
- 根據其他用戶反饋,會出莫名其妙的問題,會丟失消息。
- 目前重心放到 ActiveMQ 6.0 產品 Apollo,對 5.x 的維護較少。
- 不適合用于上千個隊列的應用場景。
RabbitMQ
RabbitMQ 于 2007 年發布,是一個在 AMQP(高級消息隊列協議)基礎上完成的,可復用的企業消息系統,是當前最主流的消息中間件之一。
主要特性如下:
- 可靠性:提供了多種技術可以讓你在性能和可靠性之間進行權衡。這些技術包括持久性機制、投遞確認、發布者證實和高可用性機制。
- 靈活的路由:消息在到達隊列前是通過交換機進行路由的。RabbitMQ 為典型的路由邏輯提供了多種內置交換機類型。
- 如果你有更復雜的路由需求,可以將這些交換機組合起來使用,你甚至可以實現自己的交換機類型,并且當做 RabbitMQ 的插件來使用。
- 消息集群:在相同局域網中的多個 RabbitMQ 服務器可以聚合在一起,作為一個獨立的邏輯代理來使用。
- 隊列高可用:隊列可以在集群中的機器上進行鏡像,以確保在硬件問題下還保證消息安全。
- 支持多種協議:支持多種消息隊列協議。
- 支持多種語言:用 Erlang 語言編寫,支持只要是你能想到的所有編程語言。
- 管理界面:RabbitMQ 有一個易用的用戶界面,使得用戶可以監控和管理消息 Broker 的許多方面。
- 跟蹤機制:如果消息異常, RabbitMQ 提供消息跟蹤機制,使用者可以找出發生了什么。
- 插件機制:提供了許多插件,來從多方面進行擴展,也可以編寫自己的插件。
- 部署環境:RabbitMQ 可以運行在 Erlang 語言所支持的平臺之上,包括 Solaris,BSD,Linux,MacOSX,TRU64,Windows 等。
使用 RabbitMQ 需要:
- ErLang 語言包
- RabbitMQ 安裝包
優點如下:
- 由于 Erlang 語言的特性,消息隊列性能較好,支持高并發。
- 健壯、穩定、易用、跨平臺、支持多種語言、文檔齊全。
- 有消息確認機制和持久化機制,可靠性高。
- 高度可定制的路由。
- 管理界面較豐富,在互聯網公司也有較大規模的應用,社區活躍度高。
缺點如下:
- 盡管結合 Erlang 語言本身的并發優勢,性能較好,但是不利于做二次開發和維護。
- 實現了代理架構,意味著消息在發送到客戶端之前可以在中央節點上排隊。此特性使得 RabbitMQ 易于使用和部署,但是使得其運行速度較慢,因為中央節點增加了延遲,消息封裝后也比較大。
- 需要學習比較復雜的接口和協議,學習和維護成本較高。
RocketMQ
RocketMQ 出自阿里的開源產品,用 Java 語言實現,在設計時參考了 Kafka,并做出了自己的一些改進,消息可靠性上比 Kafka 更好。
RocketMQ 在阿里內部被廣泛應用在訂單,交易,充值,流計算,消息推送,日志流式處理,Binglog 分發等場景。
主要特性如下:
- 基于隊列模型:具有高性能、高可靠、高實時、分布式等特點。
- Producer、Consumer 隊列都支持分布式。
- Producer 向一些隊列輪流發送消息,隊列集合稱為 Topic。Consumer 如果做廣播消費,則一個 Consumer 實例消費這個 Topic 對應的所有隊列。
- 如果做集群消費,則多個 Consumer 實例平均消費這個 Topic 對應的隊列集合。
- 能夠保證嚴格的消息順序。
- 提供豐富的消息拉取模式。
- 高效的訂閱者水平擴展能力。
- 實時的消息訂閱機制。
- 億級消息堆積能力。
- 較少的外部依賴。
部署環境:RocketMQ 可以運行在 Java 語言所支持的平臺之上。
使用 RocketMQ 需要:
- JavaJDK
- 安裝 Git、Maven
- RocketMQ 安裝包
優點如下:
- 單機支持 1 萬以上持久化隊列。
- RocketMQ 的所有消息都是持久化的,先寫入系統 PAGECACHE,然后刷盤,可以保證內存與磁盤都有一份數據,而訪問時,直接從內存讀取。
- 模型簡單,接口易用( JMS 的接口很多場合并不太實用)。
- 性能非常好,可以允許大量堆積消息在 Broker 中。
- 支持多種消費模式,包括集群消費、廣播消費等。
- 各個環節分布式擴展設計,支持主從和高可用。
- 開發度較活躍,版本更新很快。
缺點如下:
- 支持的客戶端語言不多,目前是 Java 及 C++,其中 C++ 還不成熟。
- RocketMQ 社區關注度及成熟度也不及前兩者。
- 沒有 Web 管理界面,提供了一個 CLI(命令行界面)管理工具帶來查詢、管理和診斷各種問題。
- 沒有在 MQ 核心里實現 JMS 等接口。
Kafka
Apache Kafka 是一個分布式消息發布訂閱系統。它最初由 LinkedIn 公司基于獨特的設計實現為一個分布式的日志提交系統(a distributed commit log),之后成為 Apache 項目的一部分。
Kafka 性能高效、可擴展良好并且可持久化。它的分區特性,可復制和可容錯都是不錯的特性。
主要特性如下:
- 快速持久化:可以在 O(1) 的系統開銷下進行消息持久化。
- 高吞吐:在一臺普通的服務器上即可以達到 10W/S 的吞吐速率。
- 完全的分布式系統:Broker、Producer 和 Consumer 都原生自動支持分布式,自動實現負載均衡。
- 支持同步和異步復制兩種高可用機制。
- 支持數據批量發送和拉取。
- 零拷貝技術(zero-copy):減少 IO 操作步驟,提高系統吞吐量。
- 數據遷移、擴容對用戶透明。
- 無需停機即可擴展機器。
- 其他特性:豐富的消息拉取模型、高效訂閱者水平擴展、實時的消息訂閱、億級的消息堆積能力、定期刪除機制。
部署環境,使用 Kafka 需要:
- JavaJDK
- Kafka 安裝包
優點如下:
- 客戶端語言豐富:支持 Java、.Net、PHP、Ruby、Python、Go 等多種語言。
- 高性能:單機寫入 TPS 約在 100 萬條/秒,消息大小 10 個字節。
- 提供完全分布式架構,并有 Replica 機制,擁有較高的可用性和可靠性,理論上支持消息無限堆積。
- 支持批量操作。
- 消費者采用 Pull 方式獲取消息。消息有序,通過控制能夠保證所有消息被消費且僅被消費一次。
- 有優秀的第三方 Kafka Web 管理界面 Kafka-Manager。
- 在日志領域比較成熟,被多家公司和多個開源項目使用。
缺點如下:
- Kafka 單機超過 64 個隊列/分區時, Load 時會發生明顯的飆高現象。隊列越多,負載越高,發送消息響應時間變長。
- 使用短輪詢方式,實時性取決于輪詢間隔時間。
- 消費失敗不支持重試。
- 支持消息順序,但是一臺代理宕機后,就會產生消息亂序。
- 社區更新較慢。
幾種消息隊列對比
這里列舉了上述四種消息隊列的差異對比:
Kafka 在于分布式架構,RabbitMQ 基于 AMQP 協議來實現,RocketMQ 的思路來源于 Kafka,改成了主從結構,在事務性和可靠性方面做了優化。
廣泛來說,電商、金融等對事務一致性要求很高的,可以考慮 RabbitMQ 和 RocketMQ,對性能要求高的可考慮 Kafka。
小結
本文介紹了消息隊列的特點,消息隊列的傳遞服務模型,消息的傳輸方式,消息的推拉模式。
然后介紹了 ActiveMQ,RabbitMQ,RocketMQ 和 Kafka 幾種常見的消息隊列,闡述了各種消息隊列的主要特點和優缺點。
通過本文,對于消息隊列及相關技術選型,相信你會有更深入的理解和認識。更多細節和原理性的東西,還需在實踐中見真知!
作者:陳林
簡介:五年研發與架構經驗,曾任職 SAP 中國研發中心后端研發、上海冰鑒科技信息科技有限公司架構師助理,目前擔任成都 ThoughtWorks 有限公司高級咨詢師與研發人員。熟悉大數據、高并發、負載均衡、緩存、數據庫、消息中間件、搜索引擎、容器和自動化等領域。個人學習能力強,技術熱情高,熱愛開源和寫技術博客,善于溝通和分享。