關(guān)于消息隊(duì)列,從前年開始斷斷續(xù)續(xù)看了些資料,想寫很久了,但一直沒騰出空,近來分別碰到幾個朋友聊這塊的技術(shù)選型,是時候把這塊的知識整理記錄一下了。 市面上的消息隊(duì)列產(chǎn)品有很多,比如老牌的 ActiveMQ、RabbitMQ ,目前我看最火的 Kafka ,還有 ZeroMQ ,去年底阿里巴巴捐贈給 Apache 的 RocketMQ ,連 redis 這樣的 NoSQL 數(shù)據(jù)庫也支持 MQ 功能??傊@塊知名的產(chǎn)品就有十幾種,就我自己的使用經(jīng)驗(yàn)和興趣只打算談?wù)?RabbitMQ、Kafka 和 ActiveMQ ,本文先講 RabbitMQ ,在此之前先看下消息隊(duì)列的相關(guān)概念。 什么叫消息隊(duì)列消息(Message)是指在應(yīng)用間傳送的數(shù)據(jù)。消息可以非常簡單,比如只包含文本字符串,也可以更復(fù)雜,可能包含嵌入對象。 消息隊(duì)列(Message Queue)是一種應(yīng)用間的通信方式,消息發(fā)送后可以立即返回,由消息系統(tǒng)來確保消息的可靠傳遞。消息發(fā)布者只管把消息發(fā)布到 MQ 中而不用管誰來取,消息使用者只管從 MQ 中取消息而不管是誰發(fā)布的。這樣發(fā)布者和使用者都不用知道對方的存在。 為何用消息隊(duì)列從上面的描述中可以看出消息隊(duì)列是一種應(yīng)用間的異步協(xié)作機(jī)制,那什么時候需要使用 MQ 呢? 以常見的訂單系統(tǒng)為例,用戶點(diǎn)擊【下單】按鈕之后的業(yè)務(wù)邏輯可能包括:扣減庫存、生成相應(yīng)單據(jù)、發(fā)紅包、發(fā)短信通知。在業(yè)務(wù)發(fā)展初期這些邏輯可能放在一起同步執(zhí)行,隨著業(yè)務(wù)的發(fā)展訂單量增長,需要提升系統(tǒng)服務(wù)的性能,這時可以將一些不需要立即生效的操作拆分出來異步執(zhí)行,比如發(fā)放紅包、發(fā)短信通知等。這種場景下就可以用 MQ ,在下單的主流程(比如扣減庫存、生成相應(yīng)單據(jù))完成之后發(fā)送一條消息到 MQ 讓主流程快速完結(jié),而由另外的單獨(dú)線程拉取MQ的消息(或者由 MQ 推送消息),當(dāng)發(fā)現(xiàn) MQ 中有發(fā)紅包或發(fā)短信之類的消息時,執(zhí)行相應(yīng)的業(yè)務(wù)邏輯。 以上是用于業(yè)務(wù)解耦的情況,其它常見場景包括最終一致性、廣播、錯峰流控等等。 RabbitMQ 特點(diǎn)RabbitMQ 是一個由 Erlang 語言開發(fā)的 AMQP 的開源實(shí)現(xiàn)。 AMQP :Advanced Message Queue,高級消息隊(duì)列協(xié)議。它是應(yīng)用層協(xié)議的一個開放標(biāo)準(zhǔn),為面向消息的中間件設(shè)計(jì),基于此協(xié)議的客戶端與消息中間件可傳遞消息,并不受產(chǎn)品、開發(fā)語言等條件的限制。 RabbitMQ 最初起源于金融系統(tǒng),用于在分布式系統(tǒng)中存儲轉(zhuǎn)發(fā)消息,在易用性、擴(kuò)展性、高可用性等方面表現(xiàn)不俗。具體特點(diǎn)包括:
RabbitMQ 中的概念消息模型所有 MQ 產(chǎn)品從模型抽象上來說都是一樣的過程:消費(fèi)者(consumer)訂閱某個隊(duì)列。生產(chǎn)者(producer)創(chuàng)建消息,然后發(fā)布到隊(duì)列(queue)中,最后將消息發(fā)送到監(jiān)聽的消費(fèi)者。 RabbitMQ 基本概念上面只是最簡單抽象的描述,具體到 RabbitMQ 則有更詳細(xì)的概念需要解釋。上面介紹過 RabbitMQ 是 AMQP 協(xié)議的一個開源實(shí)現(xiàn),所以其內(nèi)部實(shí)際上也是 AMQP 中的基本概念:
AMQP 中的消息路由AMQP 中消息的路由過程和 Java 開發(fā)者熟悉的 JMS 存在一些差別,AMQP 中增加了 Exchange 和 Binding 的角色。生產(chǎn)者把消息發(fā)布到 Exchange 上,消息最終到達(dá)隊(duì)列并被消費(fèi)者接收,而 Binding 決定交換器的消息應(yīng)該發(fā)送到那個隊(duì)列。 Exchange 類型Exchange分發(fā)消息時根據(jù)類型的不同分發(fā)策略有區(qū)別,目前共四種類型:direct、fanout、topic、headers 。headers 匹配 AMQP 消息的 header 而不是路由鍵,此外 headers 交換器和 direct 交換器完全一致,但性能差很多,目前幾乎用不到了,所以直接看另外三種類型:
RabbitMQ 安裝一般來說安裝 RabbitMQ 之前要安裝 Erlang ,可以去Erlang官網(wǎng)下載。接著去RabbitMQ官網(wǎng)下載安裝包,之后解壓縮即可。根據(jù)操作系統(tǒng)不同官網(wǎng)提供了相應(yīng)的安裝說明:Windows、Debian / Ubuntu、RPM-based Linux、Mac 如果是Mac 用戶,個人推薦使用 HomeBrew 來安裝,安裝前要先更新 brew: brew update 接著安裝 rabbitmq 服務(wù)器: brew install rabbitmq 這樣 RabbitMQ 就安裝好了,安裝過程中會自動其所依賴的 Erlang 。 RabbitMQ 運(yùn)行和管理
./sbin/rabbitmq-server 啟動正常的話會看到一些啟動過程信息和最后的 completed with 7 plugins,這也說明啟動的時候默認(rèn)加載了7個插件。
./sbin/rabbitmq-server -detached
./sbin/rabbitmqctl status 該命令將輸出服務(wù)器的很多信息,比如 RabbitMQ 和 Erlang 的版本、OS 名稱、內(nèi)存等等
./sbin/rabbitmqctl stop 它會和本地節(jié)點(diǎn)通信并指示其干凈的關(guān)閉,也可以指定關(guān)閉不同的節(jié)點(diǎn),包括遠(yuǎn)程節(jié)點(diǎn),只需要傳入?yún)?shù) -n : ./sbin/rabbitmqctl -n rabbit@server.example.com stop -n node 默認(rèn) node 名稱是 rabbit@server ,如果你的主機(jī)名是 server.example.com ,那么 node 名稱就是 rabbit@server.example.com 。
./sbin/rabbitmqctl stop_app 這個命令在后面要講的集群模式中將會很有用。
./sbin/rabbitmqctl start_app
./sbin/rabbitmqctl reset 該命令將清除所有的隊(duì)列。
./sbin/rabbitmqctl list_queues
./sbin/rabbitmqctl list_exchanges 該命令還可以附加參數(shù),比如列出交換器的名稱、類型、是否持久化、是否自動刪除: ./sbin/rabbitmqctl list_exchanges name type durable auto_delete
./sbin/rabbitmqctl list_bindings Java 客戶端訪問RabbitMQ 支持多種語言訪問,以 Java 為例看下一般使用 RabbitMQ 的步驟。
dependency>
package org.study.rabbitmq;
package org.study.rabbitmq;
./sbin/rabbitmq-server
RabbitMQ 集群RabbitMQ 最優(yōu)秀的功能之一就是內(nèi)建集群,這個功能設(shè)計(jì)的目的是允許消費(fèi)者和生產(chǎn)者在節(jié)點(diǎn)崩潰的情況下繼續(xù)運(yùn)行,以及通過添加更多的節(jié)點(diǎn)來線性擴(kuò)展消息通信吞吐量。RabbitMQ 內(nèi)部利用 Erlang 提供的分布式通信框架 OTP 來滿足上述需求,使客戶端在失去一個 RabbitMQ 節(jié)點(diǎn)連接的情況下,還是能夠重新連接到集群中的任何其他節(jié)點(diǎn)繼續(xù)生產(chǎn)、消費(fèi)消息。 RabbitMQ 集群中的一些概念RabbitMQ 會始終記錄以下四種類型的內(nèi)部元數(shù)據(jù):
在單一節(jié)點(diǎn)中,RabbitMQ 會將所有這些信息存儲在內(nèi)存中,同時將標(biāo)記為可持久化的隊(duì)列、交換器、綁定存儲到硬盤上。存到硬盤上可以確保隊(duì)列和交換器在節(jié)點(diǎn)重啟后能夠重建。而在集群模式下同樣也提供兩種選擇:存到硬盤上(獨(dú)立節(jié)點(diǎn)的默認(rèn)設(shè)置),存在內(nèi)存中。 如果在集群中創(chuàng)建隊(duì)列,集群只會在單個節(jié)點(diǎn)而不是所有節(jié)點(diǎn)上創(chuàng)建完整的隊(duì)列信息(元數(shù)據(jù)、狀態(tài)、內(nèi)容)。結(jié)果是只有隊(duì)列的所有者節(jié)點(diǎn)知道有關(guān)隊(duì)列的所有信息,因此當(dāng)集群節(jié)點(diǎn)崩潰時,該節(jié)點(diǎn)的隊(duì)列和綁定就消失了,并且任何匹配該隊(duì)列的綁定的新消息也丟失了。還好RabbitMQ 2.6.0之后提供了鏡像隊(duì)列以避免集群節(jié)點(diǎn)故障導(dǎo)致的隊(duì)列內(nèi)容不可用。 RabbitMQ 集群中可以共享 user、vhost、exchange等,所有的數(shù)據(jù)和狀態(tài)都是必須在所有節(jié)點(diǎn)上復(fù)制的,例外就是上面所說的消息隊(duì)列。RabbitMQ 節(jié)點(diǎn)可以動態(tài)的加入到集群中。 當(dāng)在集群中聲明隊(duì)列、交換器、綁定的時候,這些操作會直到所有集群節(jié)點(diǎn)都成功提交元數(shù)據(jù)變更后才返回。集群中有內(nèi)存節(jié)點(diǎn)和磁盤節(jié)點(diǎn)兩種類型,內(nèi)存節(jié)點(diǎn)雖然不寫入磁盤,但是它的執(zhí)行比磁盤節(jié)點(diǎn)要好。內(nèi)存節(jié)點(diǎn)可以提供出色的性能,磁盤節(jié)點(diǎn)能保障配置信息在節(jié)點(diǎn)重啟后仍然可用,那集群中如何平衡這兩者呢? RabbitMQ 只要求集群中至少有一個磁盤節(jié)點(diǎn),所有其他節(jié)點(diǎn)可以是內(nèi)存節(jié)點(diǎn),當(dāng)節(jié)點(diǎn)加入或離開集群時,它們必須要將該變更通知到至少一個磁盤節(jié)點(diǎn)。如果只有一個磁盤節(jié)點(diǎn),剛好又是該節(jié)點(diǎn)崩潰了,那么集群可以繼續(xù)路由消息,但不能創(chuàng)建隊(duì)列、創(chuàng)建交換器、創(chuàng)建綁定、添加用戶、更改權(quán)限、添加或刪除集群節(jié)點(diǎn)。換句話說集群中的唯一磁盤節(jié)點(diǎn)崩潰的話,集群仍然可以運(yùn)行,但直到該節(jié)點(diǎn)恢復(fù),否則無法更改任何東西。 RabbitMQ 集群配置和啟動如果是在一臺機(jī)器上同時啟動多個 RabbitMQ 節(jié)點(diǎn)來組建集群的話,只用上面介紹的方式啟動第二、第三個節(jié)點(diǎn)將會因?yàn)楣?jié)點(diǎn)名稱和端口沖突導(dǎo)致啟動失敗。所以在每次調(diào)用 rabbitmq-server 命令前,設(shè)置環(huán)境變量 RABBITMQ_NODENAME 和 RABBITMQ_NODE_PORT 來明確指定唯一的節(jié)點(diǎn)名稱和端口。下面的例子端口號從5672開始,每個新啟動的節(jié)點(diǎn)都加1,節(jié)點(diǎn)也分別命名為test_rabbit_1、test_rabbit_2、test_rabbit_3。 啟動第1個節(jié)點(diǎn): RABBITMQ_NODENAME=test_rabbit_1 RABBITMQ_NODE_PORT=5672 ./sbin/rabbitmq-server -detached 啟動第2個節(jié)點(diǎn): RABBITMQ_NODENAME=test_rabbit_2 RABBITMQ_NODE_PORT=5673 ./sbin/rabbitmq-server -detached 啟動第2個節(jié)點(diǎn)前建議將 RabbitMQ 默認(rèn)激活的插件關(guān)掉,否則會存在使用了某個插件的端口號沖突,導(dǎo)致節(jié)點(diǎn)啟動不成功。 現(xiàn)在第2個節(jié)點(diǎn)和第1個節(jié)點(diǎn)都是獨(dú)立節(jié)點(diǎn),它們并不知道其他節(jié)點(diǎn)的存在。集群中除第一個節(jié)點(diǎn)外后加入的節(jié)點(diǎn)需要獲取集群中的元數(shù)據(jù),所以要先停止 Erlang 節(jié)點(diǎn)上運(yùn)行的 RabbitMQ 應(yīng)用程序,并重置該節(jié)點(diǎn)元數(shù)據(jù),再加入并且獲取集群的元數(shù)據(jù),最后重新啟動 RabbitMQ 應(yīng)用程序。 停止第2個節(jié)點(diǎn)的應(yīng)用程序: ./sbin/rabbitmqctl -n test_rabbit_2 stop_app 重置第2個節(jié)點(diǎn)元數(shù)據(jù): ./sbin/rabbitmqctl -n test_rabbit_2 reset 第2節(jié)點(diǎn)加入第1個節(jié)點(diǎn)組成的集群: ./sbin/rabbitmqctl -n test_rabbit_2 join_cluster test_rabbit_1@localhost 啟動第2個節(jié)點(diǎn)的應(yīng)用程序 ./sbin/rabbitmqctl -n test_rabbit_2 start_app 第3個節(jié)點(diǎn)的配置過程和第2個節(jié)點(diǎn)類似: RABBITMQ_NODENAME=test_rabbit_3 RABBITMQ_NODE_PORT=5674 ./sbin/rabbitmq-server -detached RabbitMQ 集群運(yùn)維停止某個指定的節(jié)點(diǎn),比如停止第2個節(jié)點(diǎn): RABBITMQ_NODENAME=test_rabbit_2 ./sbin/rabbitmqctl stop 查看節(jié)點(diǎn)3的集群狀態(tài): ./sbin/rabbitmqctl -n test_rabbit_3 cluster_status |
|