關(guān)于消息隊(duì)列,從前年開始斷斷續(xù)續(xù)看了些資料啥辨,想寫很久了,但一直沒(méi)騰出空盯腌,近來(lái)分別碰到幾個(gè)朋友聊這塊的技術(shù)選型溉知,是時(shí)候把這塊的知識(shí)整理記錄一下了。
市面上的消息隊(duì)列產(chǎn)品有很多腊嗡,比如老牌的 ActiveMQ着倾、RabbitMQ 拾酝,目前我看最火的 Kafka 燕少,還有 ZeroMQ ,去年底阿里巴巴捐贈(zèng)給 Apache 的 RocketMQ 蒿囤,連 redis 這樣的 NoSQL 數(shù)據(jù)庫(kù)也支持 MQ 功能客们。總之這塊知名的產(chǎn)品就有十幾種材诽,就我自己的使用經(jīng)驗(yàn)和興趣只打算談?wù)?RabbitMQ底挫、Kafka 和 ActiveMQ ,本文先講 RabbitMQ 脸侥,在此之前先看下消息隊(duì)列的相關(guān)概念建邓。
什么叫消息隊(duì)列
消息(Message)是指在應(yīng)用間傳送的數(shù)據(jù)。消息可以非常簡(jiǎn)單睁枕,比如只包含文本字符串官边,也可以更復(fù)雜,可能包含嵌入對(duì)象外遇。
消息隊(duì)列(Message Queue)是一種應(yīng)用間的通信方式注簿,消息發(fā)送后可以立即返回,由消息系統(tǒng)來(lái)確保消息的可靠傳遞跳仿。消息發(fā)布者只管把消息發(fā)布到 MQ 中而不用管誰(shuí)來(lái)取诡渴,消息使用者只管從 MQ 中取消息而不管是誰(shuí)發(fā)布的。這樣發(fā)布者和使用者都不用知道對(duì)方的存在菲语。
為何用消息隊(duì)列
從上面的描述中可以看出消息隊(duì)列是一種應(yīng)用間的異步協(xié)作機(jī)制妄辩,那什么時(shí)候需要使用 MQ 呢惑灵?
以常見(jiàn)的訂單系統(tǒng)為例,用戶點(diǎn)擊【下單】按鈕之后的業(yè)務(wù)邏輯可能包括:扣減庫(kù)存眼耀、生成相應(yīng)單據(jù)泣棋、發(fā)紅包、發(fā)短信通知畔塔。在業(yè)務(wù)發(fā)展初期這些邏輯可能放在一起同步執(zhí)行潭辈,隨著業(yè)務(wù)的發(fā)展訂單量增長(zhǎng),需要提升系統(tǒng)服務(wù)的性能澈吨,這時(shí)可以將一些不需要立即生效的操作拆分出來(lái)異步執(zhí)行把敢,比如發(fā)放紅包、發(fā)短信通知等谅辣。這種場(chǎng)景下就可以用 MQ 修赞,在下單的主流程(比如扣減庫(kù)存、生成相應(yīng)單據(jù))完成之后發(fā)送一條消息到 MQ 讓主流程快速完結(jié)桑阶,而由另外的單獨(dú)線程拉取MQ的消息(或者由 MQ 推送消息)柏副,當(dāng)發(fā)現(xiàn) MQ 中有發(fā)紅包或發(fā)短信之類的消息時(shí),執(zhí)行相應(yīng)的業(yè)務(wù)邏輯蚣录。
以上是用于業(yè)務(wù)解耦的情況割择,其它常見(jiàn)場(chǎng)景包括最終一致性、廣播萎河、錯(cuò)峰流控等等荔泳。
RabbitMQ 特點(diǎn)
RabbitMQ 是一個(gè)由 Erlang 語(yǔ)言開發(fā)的 AMQP 的開源實(shí)現(xiàn)。
AMQP :Advanced Message Queue虐杯,高級(jí)消息隊(duì)列協(xié)議玛歌。它是應(yīng)用層協(xié)議的一個(gè)開放標(biāo)準(zhǔn),為面向消息的中間件設(shè)計(jì)擎椰,基于此協(xié)議的客戶端與消息中間件可傳遞消息支子,并不受產(chǎn)品、開發(fā)語(yǔ)言等條件的限制达舒。
RabbitMQ 最初起源于金融系統(tǒng)值朋,用于在分布式系統(tǒng)中存儲(chǔ)轉(zhuǎn)發(fā)消息,在易用性休弃、擴(kuò)展性吞歼、高可用性等方面表現(xiàn)不俗。具體特點(diǎn)包括:
可靠性(Reliability)
RabbitMQ 使用一些機(jī)制來(lái)保證可靠性塔猾,如持久化篙骡、傳輸確認(rèn)、發(fā)布確認(rèn)。靈活的路由(Flexible Routing)
在消息進(jìn)入隊(duì)列之前糯俗,通過(guò) Exchange 來(lái)路由消息的尿褪。對(duì)于典型的路由功能,RabbitMQ 已經(jīng)提供了一些內(nèi)置的 Exchange 來(lái)實(shí)現(xiàn)得湘。針對(duì)更復(fù)雜的路由功能杖玲,可以將多個(gè) Exchange 綁定在一起,也通過(guò)插件機(jī)制實(shí)現(xiàn)自己的 Exchange 淘正。消息集群(Clustering)
多個(gè) RabbitMQ 服務(wù)器可以組成一個(gè)集群摆马,形成一個(gè)邏輯 Broker 。高可用(Highly Available Queues)
隊(duì)列可以在集群中的機(jī)器上進(jìn)行鏡像鸿吆,使得在部分節(jié)點(diǎn)出問(wèn)題的情況下隊(duì)列仍然可用囤采。多種協(xié)議(Multi-protocol)
RabbitMQ 支持多種消息隊(duì)列協(xié)議,比如 STOMP惩淳、MQTT 等等蕉毯。多語(yǔ)言客戶端(Many Clients)
RabbitMQ 幾乎支持所有常用語(yǔ)言,比如 Java思犁、.NET代虾、Ruby 等等。管理界面(Management UI)
RabbitMQ 提供了一個(gè)易用的用戶界面激蹲,使得用戶可以監(jiān)控和管理消息 Broker 的許多方面棉磨。跟蹤機(jī)制(Tracing)
如果消息異常,RabbitMQ 提供了消息跟蹤機(jī)制托呕,使用者可以找出發(fā)生了什么含蓉。插件機(jī)制(Plugin System)
RabbitMQ 提供了許多插件,來(lái)從多方面進(jìn)行擴(kuò)展项郊,也可以編寫自己的插件。
RabbitMQ 中的概念模型
消息模型
所有 MQ 產(chǎn)品從模型抽象上來(lái)說(shuō)都是一樣的過(guò)程:
消費(fèi)者(consumer)訂閱某個(gè)隊(duì)列斟赚。生產(chǎn)者(producer)創(chuàng)建消息着降,然后發(fā)布到隊(duì)列(queue)中,最后將消息發(fā)送到監(jiān)聽(tīng)的消費(fèi)者拗军。
RabbitMQ 基本概念
上面只是最簡(jiǎn)單抽象的描述任洞,具體到 RabbitMQ 則有更詳細(xì)的概念需要解釋。上面介紹過(guò) RabbitMQ 是 AMQP 協(xié)議的一個(gè)開源實(shí)現(xiàn)发侵,所以其內(nèi)部實(shí)際上也是 AMQP 中的基本概念:
- Message
消息交掏,消息是不具名的,它由消息頭和消息體組成刃鳄。消息體是不透明的盅弛,而消息頭則由一系列的可選屬性組成,這些屬性包括routing-key(路由鍵)、priority(相對(duì)于其他消息的優(yōu)先權(quán))挪鹏、delivery-mode(指出該消息可能需要持久性存儲(chǔ))等见秽。 - Publisher
消息的生產(chǎn)者,也是一個(gè)向交換器發(fā)布消息的客戶端應(yīng)用程序讨盒。 - Exchange
交換器解取,用來(lái)接收生產(chǎn)者發(fā)送的消息并將這些消息路由給服務(wù)器中的隊(duì)列。 - Binding
綁定返顺,用于消息隊(duì)列和交換器之間的關(guān)聯(lián)禀苦。一個(gè)綁定就是基于路由鍵將交換器和消息隊(duì)列連接起來(lái)的路由規(guī)則,所以可以將交換器理解成一個(gè)由綁定構(gòu)成的路由表遂鹊。 - Queue
消息隊(duì)列伦忠,用來(lái)保存消息直到發(fā)送給消費(fèi)者。它是消息的容器稿辙,也是消息的終點(diǎn)昆码。一個(gè)消息可投入一個(gè)或多個(gè)隊(duì)列。消息一直在隊(duì)列里面邻储,等待消費(fèi)者連接到這個(gè)隊(duì)列將其取走赋咽。 - Connection
網(wǎng)絡(luò)連接,比如一個(gè)TCP連接吨娜。 - Channel
信道脓匿,多路復(fù)用連接中的一條獨(dú)立的雙向數(shù)據(jù)流通道。信道是建立在真實(shí)的TCP連接內(nèi)地虛擬連接宦赠,AMQP 命令都是通過(guò)信道發(fā)出去的陪毡,不管是發(fā)布消息、訂閱隊(duì)列還是接收消息勾扭,這些動(dòng)作都是通過(guò)信道完成毡琉。因?yàn)閷?duì)于操作系統(tǒng)來(lái)說(shuō)建立和銷毀 TCP 都是非常昂貴的開銷,所以引入了信道的概念妙色,以復(fù)用一條 TCP 連接桅滋。 - Consumer
消息的消費(fèi)者,表示一個(gè)從消息隊(duì)列中取得消息的客戶端應(yīng)用程序身辨。 - Virtual Host
虛擬主機(jī)丐谋,表示一批交換器、消息隊(duì)列和相關(guān)對(duì)象煌珊。虛擬主機(jī)是共享相同的身份認(rèn)證和加密環(huán)境的獨(dú)立服務(wù)器域号俐。每個(gè) vhost 本質(zhì)上就是一個(gè) mini 版的 RabbitMQ 服務(wù)器,擁有自己的隊(duì)列定庵、交換器吏饿、綁定和權(quán)限機(jī)制踪危。vhost 是 AMQP 概念的基礎(chǔ),必須在連接時(shí)指定找岖,RabbitMQ 默認(rèn)的 vhost 是 / 陨倡。 - Broker
表示消息隊(duì)列服務(wù)器實(shí)體。
AMQP 中的消息路由
AMQP 中消息的路由過(guò)程和 Java 開發(fā)者熟悉的 JMS 存在一些差別许布,AMQP 中增加了 Exchange 和 Binding 的角色兴革。生產(chǎn)者把消息發(fā)布到 Exchange 上,消息最終到達(dá)隊(duì)列并被消費(fèi)者接收蜜唾,而 Binding 決定交換器的消息應(yīng)該發(fā)送到那個(gè)隊(duì)列杂曲。
Exchange 類型
Exchange分發(fā)消息時(shí)根據(jù)類型的不同分發(fā)策略有區(qū)別,目前共四種類型:direct袁余、fanout擎勘、topic、headers 颖榜。headers 匹配 AMQP 消息的 header 而不是路由鍵棚饵,此外 headers 交換器和 direct 交換器完全一致,但性能差很多掩完,目前幾乎用不到了噪漾,所以直接看另外三種類型:
- direct
消息中的路由鍵(routing key)如果和 Binding 中的 binding key 一致, 交換器就將消息發(fā)到對(duì)應(yīng)的隊(duì)列中且蓬。路由鍵與隊(duì)列名完全匹配欣硼,如果一個(gè)隊(duì)列綁定到交換機(jī)要求路由鍵為“dog”,則只轉(zhuǎn)發(fā) routing key 標(biāo)記為“dog”的消息恶阴,不會(huì)轉(zhuǎn)發(fā)“dog.puppy”诈胜,也不會(huì)轉(zhuǎn)發(fā)“dog.guard”等等。它是完全匹配冯事、單播的模式焦匈。
-
fanout
每個(gè)發(fā)到 fanout 類型交換器的消息都會(huì)分到所有綁定的隊(duì)列上去。fanout 交換器不處理路由鍵桅咆,只是簡(jiǎn)單的將隊(duì)列綁定到交換器上括授,每個(gè)發(fā)送到交換器的消息都會(huì)被轉(zhuǎn)發(fā)到與該交換器綁定的所有隊(duì)列上。很像子網(wǎng)廣播岩饼,每臺(tái)子網(wǎng)內(nèi)的主機(jī)都獲得了一份復(fù)制的消息。fanout 類型轉(zhuǎn)發(fā)消息是最快的薛夜。
- topic
topic 交換器通過(guò)模式匹配分配消息的路由鍵屬性籍茧,將路由鍵和某個(gè)模式進(jìn)行匹配,此時(shí)隊(duì)列需要綁定到一個(gè)模式上梯澜。它將路由鍵和綁定鍵的字符串切分成單詞寞冯,這些單詞之間用點(diǎn)隔開渴析。它同樣也會(huì)識(shí)別兩個(gè)通配符:符號(hào)“#”和符號(hào)“*”。#匹配0個(gè)或多個(gè)單詞吮龄,*匹配不多不少一個(gè)單詞俭茧。
RabbitMQ 安裝
一般來(lái)說(shuō)安裝 RabbitMQ 之前要安裝 Erlang ,可以去Erlang官網(wǎng)下載漓帚。接著去RabbitMQ官網(wǎng)下載安裝包母债,之后解壓縮即可。根據(jù)操作系統(tǒng)不同官網(wǎng)提供了相應(yīng)的安裝說(shuō)明:Windows尝抖、Debian / Ubuntu毡们、RPM-based Linux、Mac
如果是Mac 用戶昧辽,個(gè)人推薦使用 HomeBrew 來(lái)安裝衙熔,安裝前要先更新 brew:
brew update
接著安裝 rabbitmq 服務(wù)器:
brew install rabbitmq
這樣 RabbitMQ 就安裝好了,安裝過(guò)程中會(huì)自動(dòng)其所依賴的 Erlang 搅荞。
RabbitMQ 運(yùn)行和管理
- 啟動(dòng)
啟動(dòng)很簡(jiǎn)單红氯,找到安裝后的 RabbitMQ 所在目錄下的 sbin 目錄,可以看到該目錄下有6個(gè)以 rabbitmq 開頭的可執(zhí)行文件咕痛,直接執(zhí)行 rabbitmq-server 即可痢甘,下面將 RabbitMQ 的安裝位置以 . 代替,啟動(dòng)命令就是:
./sbin/rabbitmq-server
啟動(dòng)正常的話會(huì)看到一些啟動(dòng)過(guò)程信息和最后的 completed with 7 plugins暇检,這也說(shuō)明啟動(dòng)的時(shí)候默認(rèn)加載了7個(gè)插件产阱。
[圖片上傳失敗...(image-773384-1537453710252)]
- 后臺(tái)啟動(dòng)
如果想讓 RabbitMQ 以守護(hù)程序的方式在后臺(tái)運(yùn)行,可以在啟動(dòng)的時(shí)候加上 -detached 參數(shù):
./sbin/rabbitmq-server -detached
- 查詢服務(wù)器狀態(tài)
sbin 目錄下有個(gè)特別重要的文件叫 rabbitmqctl 块仆,它提供了 RabbitMQ 管理需要的幾乎一站式解決方案构蹬,絕大部分的運(yùn)維命令它都可以提供。
查詢 RabbitMQ 服務(wù)器的狀態(tài)信息可以用參數(shù) status :
./sbin/rabbitmqctl status
該命令將輸出服務(wù)器的很多信息悔据,比如 RabbitMQ 和 Erlang 的版本庄敛、OS 名稱、內(nèi)存等等
- 關(guān)閉 RabbitMQ 節(jié)點(diǎn)
我們知道 RabbitMQ 是用 Erlang 語(yǔ)言寫的科汗,在Erlang 中有兩個(gè)概念:節(jié)點(diǎn)和應(yīng)用程序藻烤。節(jié)點(diǎn)就是 Erlang 虛擬機(jī)的每個(gè)實(shí)例,而多個(gè) Erlang 應(yīng)用程序可以運(yùn)行在同一個(gè)節(jié)點(diǎn)之上头滔。節(jié)點(diǎn)之間可以進(jìn)行本地通信(不管他們是不是運(yùn)行在同一臺(tái)服務(wù)器之上)怖亭。比如一個(gè)運(yùn)行在節(jié)點(diǎn)A上的應(yīng)用程序可以調(diào)用節(jié)點(diǎn)B上應(yīng)用程序的方法,就好像調(diào)用本地函數(shù)一樣坤检。如果應(yīng)用程序由于某些原因奔潰兴猩,Erlang 節(jié)點(diǎn)會(huì)自動(dòng)嘗試重啟應(yīng)用程序。
如果要關(guān)閉整個(gè) RabbitMQ 節(jié)點(diǎn)可以用參數(shù) stop :
./sbin/rabbitmqctl stop
它會(huì)和本地節(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 潭千。
- 關(guān)閉 RabbitMQ 應(yīng)用程序
如果只想關(guān)閉應(yīng)用程序,同時(shí)保持 Erlang 節(jié)點(diǎn)運(yùn)行則可以用 stop_app:
./sbin/rabbitmqctl stop_app
這個(gè)命令在后面要講的集群模式中將會(huì)很有用借尿。
- 啟動(dòng) RabbitMQ 應(yīng)用程序
./sbin/rabbitmqctl start_app
- 重置 RabbitMQ 節(jié)點(diǎn)
./sbin/rabbitmqctl reset
該命令將清除所有的隊(duì)列刨晴。
- 查看已聲明的隊(duì)列
./sbin/rabbitmqctl list_queues
- 查看交換器
./sbin/rabbitmqctl list_exchanges
該命令還可以附加參數(shù),比如列出交換器的名稱垛玻、類型割捅、是否持久化、是否自動(dòng)刪除:
./sbin/rabbitmqctl list_exchanges name type durable auto_delete
- 查看綁定
./sbin/rabbitmqctl list_bindings
Java 客戶端訪問(wèn)
RabbitMQ 支持多種語(yǔ)言訪問(wèn)帚桩,以 Java 為例看下一般使用 RabbitMQ 的步驟亿驾。
- maven工程的pom文件中添加依賴
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.1.0</version>
</dependency>
- 消息生產(chǎn)者
package org.study.rabbitmq;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//創(chuàng)建連接工廠
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername("guest");
factory.setPassword("guest");
//設(shè)置 RabbitMQ 地址
factory.setHost("localhost");
//建立到代理服務(wù)器到連接
Connection conn = factory.newConnection();
//獲得信道
Channel channel = conn.createChannel();
//聲明交換器
String exchangeName = "hello-exchange";
channel.exchangeDeclare(exchangeName, "direct", true);
String routingKey = "hola";
//發(fā)布消息
byte[] messageBodyBytes = "quit".getBytes();
channel.basicPublish(exchangeName, routingKey, null, messageBodyBytes);
channel.close();
conn.close();
}
}
- 消息消費(fèi)者
package org.study.rabbitmq;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername("guest");
factory.setPassword("guest");
factory.setHost("localhost");
//建立到代理服務(wù)器到連接
Connection conn = factory.newConnection();
//獲得信道
final Channel channel = conn.createChannel();
//聲明交換器
String exchangeName = "hello-exchange";
channel.exchangeDeclare(exchangeName, "direct", true);
//聲明隊(duì)列
String queueName = channel.queueDeclare().getQueue();
String routingKey = "hola";
//綁定隊(duì)列,通過(guò)鍵 hola 將隊(duì)列和交換器綁定起來(lái)
channel.queueBind(queueName, exchangeName, routingKey);
while(true) {
//消費(fèi)消息
boolean autoAck = false;
String consumerTag = "";
channel.basicConsume(queueName, autoAck, consumerTag, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
String routingKey = envelope.getRoutingKey();
String contentType = properties.getContentType();
System.out.println("消費(fèi)的路由鍵:" + routingKey);
System.out.println("消費(fèi)的內(nèi)容類型:" + contentType);
long deliveryTag = envelope.getDeliveryTag();
//確認(rèn)消息
channel.basicAck(deliveryTag, false);
System.out.println("消費(fèi)的消息體內(nèi)容:");
String bodyStr = new String(body, "UTF-8");
System.out.println(bodyStr);
}
});
}
}
}
- 啟動(dòng) RabbitMQ 服務(wù)器
./sbin/rabbitmq-server
運(yùn)行 Consumer
先運(yùn)行 Consumer 账嚎,這樣當(dāng)生產(chǎn)者發(fā)送消息的時(shí)候能在消費(fèi)者后端看到消息記錄莫瞬。-
運(yùn)行 Producer
接著運(yùn)行 Producer ,發(fā)布一條消息,在 Consumer 的控制臺(tái)能看到接收的消息:
RabbitMQ 集群
RabbitMQ 最優(yōu)秀的功能之一就是內(nèi)建集群郭蕉,這個(gè)功能設(shè)計(jì)的目的是允許消費(fèi)者和生產(chǎn)者在節(jié)點(diǎn)崩潰的情況下繼續(xù)運(yùn)行疼邀,以及通過(guò)添加更多的節(jié)點(diǎn)來(lái)線性擴(kuò)展消息通信吞吐量。RabbitMQ 內(nèi)部利用 Erlang 提供的分布式通信框架 OTP 來(lái)滿足上述需求召锈,使客戶端在失去一個(gè) RabbitMQ 節(jié)點(diǎn)連接的情況下旁振,還是能夠重新連接到集群中的任何其他節(jié)點(diǎn)繼續(xù)生產(chǎn)、消費(fèi)消息涨岁。
RabbitMQ 集群中的一些概念
RabbitMQ 會(huì)始終記錄以下四種類型的內(nèi)部元數(shù)據(jù):
- 隊(duì)列元數(shù)據(jù)
包括隊(duì)列名稱和它們的屬性拐袜,比如是否可持久化,是否自動(dòng)刪除 - 交換器元數(shù)據(jù)
交換器名稱梢薪、類型蹬铺、屬性 - 綁定元數(shù)據(jù)
內(nèi)部是一張表格記錄如何將消息路由到隊(duì)列 - vhost 元數(shù)據(jù)
為 vhost 內(nèi)部的隊(duì)列、交換器秉撇、綁定提供命名空間和安全屬性
在單一節(jié)點(diǎn)中甜攀,RabbitMQ 會(huì)將所有這些信息存儲(chǔ)在內(nèi)存中,同時(shí)將標(biāo)記為可持久化的隊(duì)列琐馆、交換器规阀、綁定存儲(chǔ)到硬盤上。存到硬盤上可以確保隊(duì)列和交換器在節(jié)點(diǎn)重啟后能夠重建瘦麸。而在集群模式下同樣也提供兩種選擇:存到硬盤上(獨(dú)立節(jié)點(diǎn)的默認(rèn)設(shè)置)姥敛,存在內(nèi)存中。
如果在集群中創(chuàng)建隊(duì)列瞎暑,集群只會(huì)在單個(gè)節(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)崩潰時(shí),該節(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ù)制的砍聊,例外就是上面所說(shuō)的消息隊(duì)列背稼。RabbitMQ 節(jié)點(diǎn)可以動(dòng)態(tài)的加入到集群中。
當(dāng)在集群中聲明隊(duì)列玻蝌、交換器蟹肘、綁定的時(shí)候,這些操作會(huì)直到所有集群節(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 只要求集群中至少有一個(gè)磁盤節(jié)點(diǎn)球化,所有其他節(jié)點(diǎn)可以是內(nèi)存節(jié)點(diǎn),當(dāng)節(jié)點(diǎn)加入火離開集群時(shí)翘贮,它們必須要將該變更通知到至少一個(gè)磁盤節(jié)點(diǎn)赊窥。如果只有一個(gè)磁盤節(jié)點(diǎn),剛好又是該節(jié)點(diǎn)崩潰了狸页,那么集群可以繼續(xù)路由消息锨能,但不能創(chuàng)建隊(duì)列、創(chuàng)建交換器芍耘、創(chuàng)建綁定址遇、添加用戶、更改權(quán)限斋竞、添加或刪除集群節(jié)點(diǎn)倔约。換句話說(shuō)集群中的唯一磁盤節(jié)點(diǎn)崩潰的話,集群仍然可以運(yùn)行坝初,但知道該節(jié)點(diǎn)恢復(fù)浸剩,否則無(wú)法更改任何東西钾军。
RabbitMQ 集群配置和啟動(dòng)
如果是在一臺(tái)機(jī)器上同時(shí)啟動(dòng)多個(gè) RabbitMQ 節(jié)點(diǎn)來(lái)組建集群的話,只用上面介紹的方式啟動(dòng)第二绢要、第三個(gè)節(jié)點(diǎn)將會(huì)因?yàn)楣?jié)點(diǎn)名稱和端口沖突導(dǎo)致啟動(dòng)失敗吏恭。所以在每次調(diào)用 rabbitmq-server 命令前,設(shè)置環(huán)境變量 RABBITMQ_NODENAME 和 RABBITMQ_NODE_PORT 來(lái)明確指定唯一的節(jié)點(diǎn)名稱和端口重罪。下面的例子端口號(hào)從5672開始樱哼,每個(gè)新啟動(dòng)的節(jié)點(diǎn)都加1,節(jié)點(diǎn)也分別命名為test_rabbit_1剿配、test_rabbit_2搅幅、test_rabbit_3。
啟動(dòng)第1個(gè)節(jié)點(diǎn):
RABBITMQ_NODENAME=test_rabbit_1 RABBITMQ_NODE_PORT=5672 ./sbin/rabbitmq-server -detached
啟動(dòng)第2個(gè)節(jié)點(diǎn):
RABBITMQ_NODENAME=test_rabbit_2 RABBITMQ_NODE_PORT=5673 ./sbin/rabbitmq-server -detached
啟動(dòng)第2個(gè)節(jié)點(diǎn)前建議將 RabbitMQ 默認(rèn)激活的插件關(guān)掉呼胚,否則會(huì)存在使用了某個(gè)插件的端口號(hào)沖突茄唐,導(dǎo)致節(jié)點(diǎn)啟動(dòng)不成功。
現(xiàn)在第2個(gè)節(jié)點(diǎn)和第1個(gè)節(jié)點(diǎn)都是獨(dú)立節(jié)點(diǎn)砸讳,它們并不知道其他節(jié)點(diǎn)的存在琢融。集群中除第一個(gè)節(jié)點(diǎn)外后加入的節(jié)點(diǎn)需要獲取集群中的元數(shù)據(jù),所以要先停止 Erlang 節(jié)點(diǎn)上運(yùn)行的 RabbitMQ 應(yīng)用程序簿寂,并重置該節(jié)點(diǎn)元數(shù)據(jù)漾抬,再加入并且獲取集群的元數(shù)據(jù),最后重新啟動(dòng) RabbitMQ 應(yīng)用程序常遂。
停止第2個(gè)節(jié)點(diǎn)的應(yīng)用程序:
./sbin/rabbitmqctl -n test_rabbit_2 stop_app
重置第2個(gè)節(jié)點(diǎn)元數(shù)據(jù):
./sbin/rabbitmqctl -n test_rabbit_2 reset
第2節(jié)點(diǎn)加入第1個(gè)節(jié)點(diǎn)組成的集群:
./sbin/rabbitmqctl -n test_rabbit_2 join_cluster test_rabbit_1@localhost
啟動(dòng)第2個(gè)節(jié)點(diǎn)的應(yīng)用程序
./sbin/rabbitmqctl -n test_rabbit_2 start_app
第3個(gè)節(jié)點(diǎn)的配置過(guò)程和第2個(gè)節(jié)點(diǎn)類似:
RABBITMQ_NODENAME=test_rabbit_3 RABBITMQ_NODE_PORT=5674 ./sbin/rabbitmq-server -detached
./sbin/rabbitmqctl -n test_rabbit_3 stop_app
./sbin/rabbitmqctl -n test_rabbit_3 reset
./sbin/rabbitmqctl -n test_rabbit_3 join_cluster test_rabbit_1@localhost
./sbin/rabbitmqctl -n test_rabbit_3 start_app
RabbitMQ 集群運(yùn)維
停止某個(gè)指定的節(jié)點(diǎn)纳令,比如停止第2個(gè)節(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