RabbitMQ 簡介
MQ
消息隊(duì)列疯攒,上承生產(chǎn)者,下接消費(fèi)者列荔。從生產(chǎn)者側(cè)獲取消息卸例,然后將消息轉(zhuǎn)發(fā)給消費(fèi)者。
由此可見肌毅,MQ必須具有兩個(gè)屬性:消息的緩存和路由筷转。
此外,由于MQ的存在悬而,使得原來消息的一次投遞(生產(chǎn)者 --- > 消費(fèi)者)變成了兩次投遞(生產(chǎn)者 ---> MQ ---> 消費(fèi)者)呜舒。由此,MQ就需要考慮消息投遞的可靠性問題笨奠,并由此引申出消息的防重發(fā)袭蝗、防丟失問題唤殴。
RabbitMQ
MQ有好多種,比如ActiveMQ到腥、RabbitMQ朵逝、RocketMQ、ZeroMQ乡范、Kafaka配名。不過這里只討論RabbitMQ。對于RabbitMQ晋辆,只需要知道渠脉,它是采用Erlang語言實(shí)現(xiàn)AMQP協(xié)議形成的消息中間件。
幾個(gè)概念
先看一張總覽圖
RabbitMQ Server
也叫broker server瓶佳。RabbitMQ isn't a food trunk, it's a delivery service.它的角色是維護(hù)一條從Producer到Consumer的路線芋膘,保證數(shù)據(jù)能夠按照指定的方式進(jìn)行傳輸。但是這個(gè)保證也不是100%的保證霸饲,不過對于普通的應(yīng)用來說为朋,已經(jīng)足夠了。當(dāng)然對于商業(yè)系統(tǒng)而言厚脉,再做一層數(shù)據(jù)一致性的guard潜腻,就可以徹底保證系統(tǒng)的一致性了。
Producer
生產(chǎn)者器仗。數(shù)據(jù)的發(fā)送方融涣。位于MQ的上游。
Consumer
消費(fèi)者精钮,數(shù)據(jù)的消費(fèi)方威鹿。位于MQ的下游。
exchange
where producers publish their messages轨香。
生產(chǎn)者直接將消息發(fā)送到exchange里面忽你。
Queues
where messages end up and are received by consumers。
exchange接收到消息之后臂容,會(huì)把消息投遞給對應(yīng)的Queue科雳,交由消費(fèi)者進(jìn)行處理。這里的投遞規(guī)則就是RabbitMQ的消息路由規(guī)則脓杉,下文再說糟秘。
Bindings
how the messages get routed from the exchange to particular queues.
也就是我們上面所說的路由規(guī)則。下文再進(jìn)行詳述球散。
vhost
虛擬主機(jī)尿赚。一個(gè)RabbitMQ物理服務(wù)器上可以支持有多個(gè)vhost。不同的vhost可以認(rèn)為都是一個(gè)RabbitMQ Server,可以簡單的理解為凌净,每一個(gè)vhost都是運(yùn)行在物理機(jī)上的一個(gè)虛擬機(jī)悲龟。它會(huì)擁有自己的queue、exchange冰寻、bindings等须教,并且不同的vhost單獨(dú)配置自己的權(quán)限。注意斩芭,不同的vhost轻腺,其queue和exchange不允許綁定。
Connection
就是一個(gè)TCP連接秒旋。Producer和Consumer都是通過TCP連接到RabbitMQ Server的。
Channel
虛擬連接诀拭。建立在上述的Connection之上迁筛,一個(gè)Connection支持建立多個(gè)Channel,而我們的指令耕挨,就是通過channel來告知到RabbitMQServer的细卧。一般情況下,程序起始筒占,第一步就是建立Connection贪庙,第二步就是建立Channel(當(dāng)然關(guān)閉的時(shí)候,需要先關(guān)閉channel翰苫,再關(guān)閉Connection)止邮。
注意
- 我們幾乎所有的指令都是通過channel執(zhí)行的,包括建立exchange和建立Queue的指令都是通過Channel發(fā)送到server的奏窑。但是channel和exchange或者queue沒啥關(guān)系导披,可以認(rèn)為channel完全就是一個(gè)命令管道,主要為了復(fù)用TCP連接埃唯,除此之外撩匕,沒啥其他特殊的地方。
- channel算是一種虛擬連接墨叛,只要TCP連接扛得住止毕,創(chuàng)建多少都沒關(guān)系。
- channel其實(shí)是可以支持多線程的漠趁,但是我們往往讓一個(gè)channel只負(fù)責(zé)一個(gè)線程扁凛。當(dāng)channel用于多線程時(shí),其接收到的指令會(huì)進(jìn)行排序闯传,依次執(zhí)行令漂。所以,可以認(rèn)為只能保證一定程度的線程安全(類似SynchronizedMap與SynchronizedList)。
channel存在的必要性
因?yàn)閷τ诜?wù)器來說叠必,TCP鏈接也算是一種比較昂貴的資源(TCP連接數(shù)是有限制的荚孵,一定程度上限制了系統(tǒng)的并發(fā)能力),同時(shí)纬朝,對TCP連接的頻繁建立和刪除收叶,也是相當(dāng)耗費(fèi)資源的。所以共苛,引入了channel的概念判没,用以對Connection進(jìn)行高度復(fù)用。
ConnectionFactory
建立Connection的工廠
消費(fèi)模式
對于MQ來說隅茎,有兩種經(jīng)典的消費(fèi)模式:推澄峰、拉。
部分MQ只支持推模式或者拉模式辟犀。而RabbitMQ同時(shí)支持兩種模式俏竞。
推和拉兩種模式都是針對消費(fèi)者而言的。
拉
拉模式的話堂竟,就是消費(fèi)者根據(jù)自己的意愿魂毁,主動(dòng)從MQ中獲取消息。MQ將不會(huì)主動(dòng)將消息告知到消費(fèi)者出嘹。
核心的方法就是:
GetResponse basicGet(String queue, Boolean autoACK);
代碼示例如下:
public class ReceiverByGet {
public final static String QUEUE_NAME = "hello";
public static void main(String[] args) {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
while(true) {
// 這里才是重點(diǎn)
GetResponse response = channel.basicGet(QUEUE_NAME, true);
if (response = null) {
System.out.println("Get Nothing");
TimeUnit.MILLISECONDS.sleep(1000);
} else {
String message = new String(response.getBody(), "UTF-8");
System.out.println(message);
TimeUnit.MILLISECONDS.sleep(500);
}
}
}
}
推
顧名思義席楚,推模式情況下, MQ主導(dǎo)什么情況下向消費(fèi)者發(fā)送消息税稼,以及每次發(fā)送多少消息烦秩。
而由于是MQ進(jìn)行主導(dǎo),在沒有任何控制的情況下郎仆,很有可能會(huì)對消費(fèi)者產(chǎn)生壓力闻镶。基于此丸升,MQ往往會(huì)提供一個(gè)字段铆农,用來標(biāo)識(shí)所允許的最大未消費(fèi)(也就是未ack)的消息數(shù)。一旦未消費(fèi)的消息數(shù)超過這個(gè)數(shù)值狡耻,將不會(huì)再往消費(fèi)者推送消息墩剖。RabbitMQ里面同樣有這么一個(gè)字段(叫做prefatchCount,是設(shè)置在channel上面的岭皂, 默認(rèn)是1)。
推拉模式對比
顯而易見楷扬,使用推模式的話镣衡,必須謹(jǐn)慎的設(shè)置prefatchCount字段奴烙,不然要么有可能會(huì)導(dǎo)致消費(fèi)者消費(fèi)能力不足導(dǎo)致被壓垮倒庵,要么因?yàn)閜refatchCount字段過小導(dǎo)致系統(tǒng)處理消息的能力受限。
但是书在,使用拉模式的話,消費(fèi)者可以根據(jù)自己的能力來決定消費(fèi)速率儒旬,但是在消費(fèi)遲緩的時(shí)候栏账,很容易造成消息的堆積。并且栈源,有可能會(huì)導(dǎo)致消息從生產(chǎn)到消費(fèi)的時(shí)間過長挡爵,在處理存在有效期的消息時(shí),會(huì)造成一定的影響甚垦。
路由方式
如上文所述茶鹃,MQ必須擁有兩個(gè)功能:消息的緩存和路由。這里就討論一下RabbitMQ所支持的消息路由方式艰亮。
(這里所說的消息路由是指從exchange向queue中消息的轉(zhuǎn)發(fā))
direct
direct模式下闭翩,exchange根據(jù)消息的routing key來將消息分發(fā)給不同的queue。其工作流程如下:
- 聲明一個(gè)queue迄埃,并且用K作為routing key綁定到direct模式的exchange上
- exchange接收到以R作為routing key的消息時(shí)疗韵,如果K==R,直接轉(zhuǎn)發(fā)到對應(yīng)的queue上
注意
- 即使direct經(jīng)常用在單播的場景下侄非,但是如果有兩個(gè)queue使用同一個(gè)routing key綁定到同一個(gè)exchange上(如上圖所示)蕉汪,那么這一條消息會(huì)在每一個(gè)queue發(fā)送一份。
-
實(shí)際使用中逞怨,往往會(huì)有如下的使用情況者疤,一個(gè)queue用兩個(gè)routing key掛載到exchange上面:
image.png
fanout
fanout模式又往往被認(rèn)為是廣播模式。如下圖所示叠赦。
fanout模式的exchange會(huì)把消息發(fā)送到所有綁定到這個(gè)exchange上面的queue里面去驹马。
也就是說,假設(shè)有N個(gè)queue綁定到同一個(gè)fanout的exchange上除秀,這個(gè)exchange每接收到一條消息糯累,就會(huì)向這N個(gè)queue分別發(fā)送一條消息。
topic
topic與direct模式有些相似鳞仙,都是exchange拿到消息寇蚊,之后會(huì)根據(jù)routing key,將消息發(fā)送到routing key相匹配并且綁定到了這個(gè)exchange上的queue里面去(如果有多個(gè)queue的routing key都匹配棍好,那么這些queue都會(huì)收到消息)仗岸。不過topic與direct的一個(gè)區(qū)別在于:topic模式下允耿,queue向exchange綁定所用的routing key是支持模糊匹配的,其中*可以匹配一個(gè)標(biāo)志符,#可以匹配零個(gè)或多個(gè)字符。比如消息的 routing key為callcenter.agent.C1.call月杉,那么queue做綁定時(shí)使用如下的routing key都能夠獲取到消息:callcenter.agent.#、callcenter.agent.C1.*蚂蕴、callcenter.agent.*.call。
根據(jù)topic的性質(zhì)俯邓,此模式經(jīng)常用做消息的多播模型骡楼。
header
上面所說的三種是最經(jīng)常使用的模式,此外稽鞭,還有一種header模式鸟整。
header模式的exchange在路由消息時(shí),是不會(huì)使用routing key作為路由依據(jù)的朦蕴,它的路由依據(jù)為消息的headers屬性(并且只有在完全匹配的時(shí)候才會(huì)進(jìn)行路由篮条,否則是不會(huì)向這些queue投遞消息的,消息會(huì)進(jìn)入Alternate Exchanges吩抓,后面會(huì)詳述)涉茧。
消息的可靠性投遞
消息的確認(rèn)機(jī)制
生產(chǎn)側(cè)的消息確認(rèn)
消息從生產(chǎn)者產(chǎn)生,交給RabbitMQ疹娶,但是由于網(wǎng)絡(luò)的不確定性伴栓,消息是有可能交付失敗的,所以RabbitMQ提供了兩種模式來保證生產(chǎn)者側(cè)的消息可靠性投遞蚓胸。
事務(wù)模式
這里的事務(wù)有三個(gè)核心方法:
channel.txSelect(); // 開啟事務(wù)
channel.txCommint(); // 事務(wù)提交
channel.txRollback(); //事務(wù)回滾
只有成功地將消息交付給RabbitMQ挣饥,事務(wù)才算完成除师。顯而易見沛膳,使用事務(wù)會(huì)對性能造成影響。
使用示例:
try {
channel.txSelect();
channel.basicPublish(exchange, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes());
channel.txCommint();
} catch(Exception ex) {
ex.printStackTrace();
channel.txRollback();
}
confirm模式
原理
生產(chǎn)者將信道設(shè)置成confirm模式汛聚,一旦信道設(shè)置成confirm模式锹安,所有在該信道上面發(fā)布的消息都會(huì)被指派一個(gè)唯一的ID(從1開始),一旦消息被投遞到所有匹配的隊(duì)列后倚舀,broker就會(huì)發(fā)送一個(gè)確認(rèn)給生產(chǎn)者(包含消息的唯一ID)叹哭,這就使得生產(chǎn)者知道消息已經(jīng)正確到達(dá)目的隊(duì)列了。如果消息和隊(duì)列是持久化的(持久化的內(nèi)容下文再說)痕貌,那么確認(rèn)消息會(huì)在消息寫入磁盤后再發(fā)出风罩。broker回傳給生產(chǎn)者的確認(rèn)消息中deliver-tag域包含了確認(rèn)消息的序列號。此外舵稠,broker也可以設(shè)置basic.ack的multiple域超升,表示到這個(gè)序列號之前的所有消息都已經(jīng)得到了處理入宦。
confirm模式最大的好處在于它可以是異步的,一旦發(fā)布一條消息室琢,生產(chǎn)者的應(yīng)用程序就可以在等信道確認(rèn)的同時(shí)繼續(xù)發(fā)送下一條消息乾闰,當(dāng)消息最終得到確認(rèn)之后,生產(chǎn)者應(yīng)用便可以通過回調(diào)方法來處理該確認(rèn)消息盈滴,如果RabbitMQ因?yàn)樽陨韮?nèi)部錯(cuò)誤導(dǎo)致消息丟失涯肩,就會(huì)發(fā)送一條nack消息,生產(chǎn)者應(yīng)用程序同樣可以在回調(diào)方法中處理該nack消息巢钓。
在channel被設(shè)置成confirm模式之后病苗,所有被publish的后續(xù)消息都將被confirm(即ack)或者被nack一次。但是沒有對消息被confirm的快慢做任何保證症汹,并且同一條消息不回既被ack又被nack
铅乡。
同時(shí),confirm模式和事務(wù)模式不允許同時(shí)存在
烈菌。
confirm方式又分為三種:單條confirm阵幸、批量confirm、異步confirm
單條confirm
又稱普通confirm模式芽世。每發(fā)送一條消息挚赊,調(diào)用waitForConfirms()方法,等待MQ服務(wù)器端confirm济瓢。這實(shí)際上算是一種串行confirm了荠割。
如果服務(wù)器端返回false或者超時(shí)未返回,生產(chǎn)者會(huì)對這條消息進(jìn)行重傳旺矾。
代碼示例:
channel.basicPublish(ConfirmConfig.exchangeName, confirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes());
if (!channel.waitForConfirms()) {
System.out.println("send message failed");
}
批量confirm
每發(fā)送一批消息后蔑鹦,調(diào)用waitForConfirms()方法,等待服務(wù)器端confirm箕宙。
批量confirm比單條confirm稍微復(fù)雜一些嚎朽。生產(chǎn)者程序需要定期(每隔多少秒)或者定量(達(dá)到多少條)或者兩者結(jié)合起來publish消息,然后等待服務(wù)器端進(jìn)行confirm柬帕。相比于單條confirm模式哟忍,批量confirm極大地提升了confirm效率。但是問題在于陷寝,一旦出現(xiàn)confirm返回false或者超時(shí)的情況锅很,生產(chǎn)者需要將這一批次的消息全部重發(fā),這會(huì)明顯會(huì)帶來消息重復(fù)凤跑。并且爆安,在confirm消息經(jīng)常丟失的情況下,批量confirm的性能應(yīng)該是不升反降的仔引。
代碼示例:
channel.confrimSelect();
for (int i=0; i<batchCount; i++) {
channel.basicPublish(ConfirmConfig.exchangeName, ConfirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes());
if (!channel.waitForConfirms()) {
System.out.println("send message failed");
}
}
異步confirm
提供一個(gè)回調(diào)方法扔仓,服務(wù)器端confirm了一條或者多條消息后client端會(huì)回調(diào)這個(gè)方法致扯。
異步confirm模式的編程實(shí)現(xiàn)最為復(fù)雜。Channel對象提供的ConfirmListener()回調(diào)方法只包含deliveryTag(當(dāng)前Channel發(fā)出的消息序號)当辐,我們需要自己為每一個(gè)Channel維護(hù)一個(gè)unconfirm的消息序號集合抖僵,每publish一條消息,集合中元素加1缘揪;每回調(diào)一次handleAck方法耍群,unconfirm集合刪掉相應(yīng)的一條(multiple = false)或多條(multiple = true)記錄。從程序運(yùn)行效率上看找筝,這個(gè)unconfirm集合最好采用有序集合SortedSet存儲(chǔ)結(jié)構(gòu)蹈垢。實(shí)際上,SDK中的waitForConfirms()方法也是通過SortedSet維護(hù)消息序號的袖裕。
關(guān)鍵代碼:
SortedSet<Long> confirmSet = Collections.SynchronizedSortedSet(new TreeSet<Long>());
channel.confirmSelect();
channel.addConfirmListener(new ConfirmListener(){
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
if (multiple) {
confirm.headSet(deliveryTag + 1).clear();
} else {
confirmSet.remove(deliveryTag);
}
}
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("Nack, SeqNo: " + deliveryTag + ", multiple: " + multiple);
if (multiple) {
confirmSet.headSet(deliveryTag + 1).clear();
} else {
confirmSet.remove(deliveryTag);
}
}
});
while(true) {
long nextSeqNo = channel.getNextPublishSeqNo();
channel.basicPublish(ConfirmConfig.exchangeName, ConfirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes());
confirmSet.add(nextSeqNo);
}
性能比較
事務(wù)模式 < 單條confirm << 批量confirm < 異步confirm
使用事務(wù)模式曹抬,性能是最差的。單條confirm模式性能會(huì)比事務(wù)稍微好一些急鳄。但是和批量confirm模式還有異步confirm模式相比谤民,就差的遠(yuǎn)了。批量confirm模式的問題在于confirm頻繁返回false進(jìn)行大量消息重發(fā)時(shí)會(huì)使性能降低疾宏,異步confirm模式(async)編程模型較為復(fù)雜张足,至于要采用哪種方式,視情況而定坎藐,自己選擇为牍。
消費(fèi)側(cè)的消息確認(rèn)
為了保證消息成功到達(dá)消費(fèi)者進(jìn)行消費(fèi),RabbitMQ同樣對消費(fèi)側(cè)存在消息的確認(rèn)機(jī)制岩馍。
消費(fèi)者在聲明隊(duì)列時(shí)碉咆,可以指定noAck參數(shù),當(dāng)noAck = false時(shí)蛀恩,RabbitMQ會(huì)等待消費(fèi)者顯式發(fā)回ack信號才會(huì)從內(nèi)存(和磁盤疫铜,如果是持久化消息的話)中移除消息。否則赦肋,RabbitMQ會(huì)在隊(duì)列中消息被消費(fèi)后立即進(jìn)行刪除块攒。
三種模式:
- AcknowledgeMode.NONE:無需確認(rèn)励稳。在消息發(fā)送到消費(fèi)者時(shí)佃乘,就進(jìn)行ack,也就認(rèn)為消費(fèi)成功了驹尼。這種情況下趣避,只有負(fù)責(zé)隊(duì)列的進(jìn)程出現(xiàn)了異常,才會(huì)進(jìn)行nack新翎,其他情況都是ack程帕。
- AcknowledgeMode.AUTO:根據(jù)情況ack住练。如果消費(fèi)過程中沒有拋出異常,就認(rèn)為消費(fèi)成功了愁拭,也就進(jìn)行ack讲逛。這里有幾個(gè)異常會(huì)存在特殊處理:AmqpRejectAndDontRequeueException,會(huì)認(rèn)為消息失敗岭埠,拒絕消息盏混,并且requeue = false(也就是不再重新投遞);ImmediateAcknowledgeAmqpException惜论,會(huì)進(jìn)行ack许赃;其他的異常均進(jìn)行nack,同時(shí)requeue = true(也就是進(jìn)行重新投遞)馆类。
- AcknowledgeMode.MANUAL:手動(dòng)ack混聊。上面兩種都是不需要關(guān)心ack方法調(diào)用的,但是使用手動(dòng)ack時(shí)乾巧,必須要手動(dòng)調(diào)用ack方法句喜。不過,手動(dòng)ack是支持批量處理的(可以設(shè)置是否進(jìn)行批量ack)沟于,這樣可以減小IO消耗藤滥。不過它有著與批量confirm相同的問題。
注意
- 只有收到了ack消息社裆,MQ才會(huì)從存儲(chǔ)中把這一條消息給移除拙绊。
- 對于消費(fèi)者這一側(cè)的消息確認(rèn),RabbitMQ是沒有設(shè)置超時(shí)時(shí)間的泳秀,理由是并不能確認(rèn)消息的消費(fèi)時(shí)長标沪。但是一旦消費(fèi)者的鏈接斷開,這些沒有ack的消息是會(huì)被重新投遞的嗜傅。換句話說金句,如果一個(gè)消費(fèi)者掛掉(與MQ的連接斷開),沒有ack的消息會(huì)進(jìn)行重新投遞吕嘀,但是假如這個(gè)消費(fèi)者沒有掛掉违寞,那么會(huì)一直等待ack,沒有超時(shí)時(shí)間的限制偶房。
- queue中一個(gè)消息每一次發(fā)送給消費(fèi)者后只能被ack或者nack一次趁曼。如果超過一次,會(huì)報(bào)出通道(channel)異常棕洋。這是因?yàn)閏hannel對于其中的每一個(gè)消息都會(huì)進(jìn)行編號(回憶一下上面異步confirm所講的內(nèi)容挡闰,很類似)。同理,如果ack或者nack了一個(gè)從未出現(xiàn)過的編號摄悯,同樣會(huì)拋出異常赞季。
- ack與nack的一些內(nèi)容:
- ack代表消息進(jìn)行了正常消費(fèi)
- nack和reject其實(shí)是一樣的,都是代表消息并沒有被正常消費(fèi)奢驯,其區(qū)別在于reject每次只能處理一條消息
- nack和reject之后申钩,消息的處理策略是依據(jù)requeue而定的。如果requeue為true瘪阁,則消息會(huì)被重新投遞典蜕,再次嘗試進(jìn)行消費(fèi)(如果存在多個(gè)消費(fèi)者,那么有可能不是之前的消費(fèi)者拿到這個(gè)消息)罗洗;如果requeue = false愉舔,則會(huì)判斷當(dāng)前隊(duì)列是否設(shè)置了Dead Letter Exchange,如果有設(shè)置伙菜,消息會(huì)進(jìn)入這個(gè)exchange轩缤,如果沒有設(shè)置,消息會(huì)被直接移除贩绕。
持久化機(jī)制
持久化主要為了解決RabbitMQ在異常情況下的可靠性問題(比如直接crash了)火的。
持久化的作用就是,把指定的內(nèi)容保存到磁盤上淑倾,這樣即使在MQ服務(wù)器崩潰的時(shí)候馏鹤,重啟之后也能重新從磁盤中讀取出來,恢復(fù)之前的內(nèi)容娇哆。
持久化可以分為三部分:Exchange的持久化湃累、Queue的持久化、消息的持久化碍讨。
加深理解:
- exchange治力、queue、消息這三部分的持久化沒什么關(guān)聯(lián)勃黍,誰聲明為持久化宵统,服務(wù)器重啟之后,就會(huì)對誰進(jìn)行重新構(gòu)建覆获。不過马澈,如果想恢復(fù)整個(gè)的流程,最好對這三者都進(jìn)行持久化(當(dāng)然弄息,持久化是會(huì)效率有損耗的)痊班。
- Queue和exchange都可以在創(chuàng)建的時(shí)候直接聲明為持久化的(declare方法中的一個(gè)參數(shù))。而消息是在發(fā)送的時(shí)候設(shè)置為持久化的(basicPublish的一個(gè)參數(shù))疑枯。
- 不需要為Binding持久化辩块,只要對exchange和queue設(shè)置為持久化的,在恢復(fù)的時(shí)候荆永,binding會(huì)自動(dòng)恢復(fù)(其實(shí)可以理解為binding本身只是一個(gè)虛擬概念废亭,實(shí)際是依仗于exchange的模式以及queue的routing key設(shè)置才建立的)。
- RabbitMQ不會(huì)允許一個(gè)非持久化的exchange和一個(gè)持久化的queue進(jìn)行綁定具钥,反之亦然豆村。想要成功綁定,exchange和queue必須都是持久化的骂删,或者都是非持久化的掌动。
- 一旦創(chuàng)建了exchange或者queue之后,是不允許進(jìn)行修改的宁玫,要想修改只能刪除重建粗恢。所以,要將一個(gè)非持久化的隊(duì)列變成持久化的欧瘪,唯一的辦法只能刪除這個(gè)隊(duì)列之后重新建立眷射。
- 在不設(shè)置持久化的情況下,消息是會(huì)放在內(nèi)存中的佛掖,不會(huì)落到磁盤上(所以重啟時(shí)會(huì)丟消息)妖碉。
- 如果生產(chǎn)者這一側(cè)開啟了事務(wù)模式,消息是一定會(huì)被持久化的芥被。只有在消息刷到磁盤上欧宜,才會(huì)告知成功。
其他:
- 將Queue拴魄、exchange冗茸、消息都設(shè)置成持久化的,就能保證消息100%不丟了嗎匹中?
當(dāng)然不是蚀狰。
一個(gè)關(guān)鍵的問題就在于,在生產(chǎn)者這一側(cè)职员,如果沒有采用事務(wù)模式麻蹋,消息在進(jìn)入RabbitMQ之后,并不是立刻落到磁盤上的焊切,這里有一個(gè)刷盤時(shí)機(jī)的限制扮授。如果這時(shí)服務(wù)器宕機(jī)了,那么這部分沒有刷盤的消息自然會(huì)被丟了专肪。當(dāng)然刹勃,前面也說了,這是生產(chǎn)者這一側(cè)沒有采用事務(wù)模式的情況下的問題嚎尤,如果設(shè)置了事務(wù)荔仁,只有在消息刷到磁盤上,才算是正常結(jié)束。 - 消息什么時(shí)間會(huì)刷到磁盤上乏梁?
前提:生產(chǎn)側(cè)非事務(wù)場景次洼。
RabbitMQ會(huì)有一個(gè)buffer,大小為1M遇骑,生產(chǎn)者產(chǎn)生的數(shù)據(jù)進(jìn)入MQ的時(shí)候卖毁,會(huì)先進(jìn)入這個(gè)buffer。只有當(dāng)buffer寫滿之后落萎,才會(huì)將buffer里面的內(nèi)容寫入文件中(這里仍不一定立即寫入磁盤中)亥啦。
此外,還有一個(gè)固定的刷盤周期:25ms练链,也就是不管buffer有沒有滿翔脱,每隔25ms,都會(huì)進(jìn)行一次刷盤操作媒鼓,將buffer里面的內(nèi)容與未刷入磁盤的文件內(nèi)容落到磁盤上届吁。
負(fù)載均衡
在MQ集群中,往往不會(huì)只有一臺(tái)機(jī)器隶糕,這樣的話瓷产,每一個(gè)客戶端要具體連接到那一臺(tái)服務(wù)器就需要進(jìn)行控制。舉個(gè)例子枚驻,假設(shè)集群中三臺(tái)機(jī)器濒旦,但是所有的連接都打到了其中的一臺(tái)機(jī)器上,這樣的話再登,整個(gè)系統(tǒng)的性能和可靠性顯然是有問題的尔邓。
實(shí)際上,對于RabbitMQ而言锉矢,可以在客戶端連接時(shí)簡單地使用一些算法來實(shí)現(xiàn)負(fù)載均衡梯嗽,這一類的算法有很多種,這里說下常見的一些:
輪詢法
將請求按順序輪流地分配到后端服務(wù)器上沽损,它均衡地對待后端的每一臺(tái)服務(wù)器灯节,而不關(guān)心服務(wù)器實(shí)際的連接數(shù)和當(dāng)前的系統(tǒng)負(fù)載。
代碼示例:
public class RoundRobin {
private static List<String> list = new ArrayList<String>() {{
add("192.168.0.2");
add("192.168.0.3");
add("192.168.0.4");
}};
private static int pos = 0;
private static final Object lock = new Object();
public static String getConnectionAddress() {
String ip = null;
synchronized(lock) {
ip = list.get(pos);
if (++pos >= list.size()) {
pos = 0;
}
}
return ip;
}
}
隨機(jī)法
通過隨機(jī)算法绵估,根據(jù)后端服務(wù)器的列表大小值來隨機(jī)選取其中的一臺(tái)服務(wù)器進(jìn)行訪問炎疆。由概率統(tǒng)計(jì)理論可以得知,隨著客戶端調(diào)用服務(wù)端的次數(shù)增多国裳,其實(shí)際效果越來越接近于平均分配(也就是輪詢分配)形入。
代碼示例:
public class RandomAccess {
private static List<String> list = new ArrayList<String>(){{
add("192.168.0.2");
add("192.168.0.3");
add("192.168.0.4");
}};
public static String getConnectionAccess() {
Random random = new Random();
int pos = random.nextInt(list.size());
return list.get(pos);
}
}
源地址哈希法
源地址哈希的思想是根據(jù)獲取的客戶端ip地址,通過哈希函數(shù)計(jì)算到一個(gè)數(shù)值缝左,用該數(shù)值對服務(wù)器列表的大小進(jìn)行取模運(yùn)算亿遂,得到的結(jié)果便是客戶端要訪問服務(wù)器的序號浓若。采用源地址哈希法進(jìn)行負(fù)載均衡,同一ip地址的客戶端蛇数,當(dāng)后端服務(wù)器列表不變時(shí)挪钓,它每次都會(huì)映射到同一臺(tái)后端服務(wù)器進(jìn)行訪問。
代碼示例:
public class IpHash {
private static List<String> list = new ArrayList<String>(){{
add("192.168.0.2");
add("192.168.0.3");
add("192.168.0.4");
}};
public static String getConnectionAccess() throws UnknownHostException {
int ipHashCode = InetAddress.getLocalHost().getHostAddress().hashCode();
int pos = ipHashCode % list.size();
return list.get(pos);
}
}
加權(quán)輪詢法
不同的服務(wù)器可能機(jī)器的配置和當(dāng)前系統(tǒng)的負(fù)載并不相同苞慢,因此它們的抗壓能力也不相同诵原。給配置高英妓、負(fù)載低的機(jī)器配置更高的權(quán)重挽放,讓其處理更多的請求;而配置低蔓纠、負(fù)載高的機(jī)器辑畦,給其分配較低的權(quán)重,降低其系統(tǒng)負(fù)載腿倚。加權(quán)輪詢能夠很好地處理這一問題纯出,并將請求順序且按照權(quán)重分配到后端。
加權(quán)隨機(jī)法
與加權(quán)輪詢法一樣敷燎,加權(quán)隨機(jī)法也根據(jù)后端機(jī)器的配置暂筝、系統(tǒng)的負(fù)載分配不同的權(quán)重。不同的是硬贯,它是按照權(quán)重隨機(jī)請求后端服務(wù)器焕襟,而非順序。
最小連接數(shù)法
最小連接數(shù)算法比較靈活和智能饭豹,由于服務(wù)器的配置不盡相同鸵赖,對于請求的處理有快有慢,它是根據(jù)服務(wù)器當(dāng)前的連接情況拄衰,動(dòng)態(tài)地選擇其中當(dāng)前積壓連接數(shù)最少的一臺(tái)服務(wù)器來處理當(dāng)前的請求它褪,盡可能地提高服務(wù)器的利用效率,將負(fù)載合理地分流到每一臺(tái)服務(wù)器翘悉。
集群
這里只討論cluster模式的集群茫打。
首先,需要了解一個(gè)點(diǎn):RabbitMQ是基于Erlang實(shí)現(xiàn)的妖混,而Erlang天生是具有分布式特性的(基于Erlang的magic cookie)老赤,所以對于RabbitMQ而言,實(shí)現(xiàn)集群非常簡單源葫,并不像其他的MQ那樣實(shí)現(xiàn)集群需要zk等诗越。
這里只討論兩種模式的集群:默認(rèn)模式、鏡像隊(duì)列模式息堂。
元數(shù)據(jù)
在集群中嚷狞,不同的節(jié)點(diǎn)會(huì)彼此同步四種類型的元數(shù)據(jù):
- 隊(duì)列元數(shù)據(jù):隊(duì)列名稱和它的屬性
- 交換器元數(shù)據(jù):交換器名稱块促、類型和屬性
- 綁定元數(shù)據(jù):一張簡單的表格展示了如何將消息路由到queue
- vhost元數(shù)據(jù):為vhost內(nèi)的隊(duì)列、交換器和綁定提供命名空間和安全屬性床未。
所以竭翠,可以認(rèn)為在每一個(gè)節(jié)點(diǎn)通過rabbitmqctl指令查詢到的queue/user/exchange/vhost信息都是相同的。
此外薇搁,在RabbitMQ集群中斋扰,會(huì)有多個(gè)節(jié)點(diǎn),這些節(jié)點(diǎn)有內(nèi)存節(jié)點(diǎn)和磁盤節(jié)點(diǎn)之分啃洋。顧名思義传货,內(nèi)存節(jié)點(diǎn),就是上面所說的同步的內(nèi)容都放到了內(nèi)存中宏娄,而磁盤節(jié)點(diǎn)就是這些信息都會(huì)放到磁盤中问裕。當(dāng)然,如果將exchange和queue設(shè)置為持久化的孵坚,即使是在內(nèi)存節(jié)點(diǎn)上粮宛,這些信息也是會(huì)持久化到磁盤上的。
這里有兩個(gè)注意點(diǎn):
- 如果只有一個(gè)節(jié)點(diǎn)卖宠,則只能設(shè)置為磁盤節(jié)點(diǎn)巍杈。
- RabbitMQ集群中至少要有一個(gè)磁盤節(jié)點(diǎn),所有其他的節(jié)點(diǎn)都可以是內(nèi)存節(jié)點(diǎn)扛伍。在這種情況下筷畦,所有元數(shù)據(jù)有改動(dòng)時(shí),必須通知到磁盤節(jié)點(diǎn)蜒秤,由其進(jìn)行落盤處理汁咏。一旦磁盤節(jié)點(diǎn)掛掉,MQ可以正常運(yùn)轉(zhuǎn)作媚,但是不會(huì)允許進(jìn)行元數(shù)據(jù)的增刪改查
- 解決這種問題的一個(gè)思路是攘滩,使用雙磁盤節(jié)點(diǎn),那么只會(huì)在兩個(gè)節(jié)點(diǎn)都掛掉的時(shí)候纸泡,才會(huì)出現(xiàn)這個(gè)問題漂问,而出現(xiàn)這種情況的幾率就比較低了。
- RabbitMQ節(jié)點(diǎn)啟動(dòng)時(shí)女揭,不指定的話蚤假,默認(rèn)是磁盤節(jié)點(diǎn)。
- 其實(shí)RabbitMQ文檔表示磁盤節(jié)點(diǎn)比內(nèi)存節(jié)點(diǎn)性能差不了多少吧兔,建議都是用磁盤節(jié)點(diǎn)磷仰。
默認(rèn)模式
下圖展示了默認(rèn)模式下的各節(jié)點(diǎn)示意圖。各個(gè)節(jié)點(diǎn)之間只會(huì)復(fù)制隊(duì)列元數(shù)據(jù)境蔼,不會(huì)同步隊(duì)列的消息內(nèi)容灶平。
默認(rèn)的集群模式下伺通,對于exchenge的拷貝,是鏡像拷貝逢享,每個(gè)節(jié)點(diǎn)的內(nèi)容完全一致罐监,所有節(jié)點(diǎn)共享相同的exchange信息。此外瞒爬,queue的元數(shù)據(jù)也會(huì)在各個(gè)節(jié)點(diǎn)之間進(jìn)行同步弓柱,但是隊(duì)列內(nèi)的數(shù)據(jù)不會(huì)參與同步。
可以這么理解:MQ在從生產(chǎn)者那里拿到消息之后侧但,消息是會(huì)先進(jìn)入exchange的矢空,而exchange會(huì)立即將消息路由到queue里面去,如果消費(fèi)者消費(fèi)能力不足俊犯,消息就會(huì)堆積在queue里面妇多。換句話說伤哺,MQ的消息緩存能力主要由queue來實(shí)現(xiàn)燕侠。而這里的默認(rèn)模式,每個(gè)節(jié)點(diǎn)之間會(huì)同步所有的exchange信息立莉,并且會(huì)復(fù)制隊(duì)列元數(shù)據(jù)绢彤,所以每一個(gè)節(jié)點(diǎn)都知道exchange和queue的所有屬性,因此蜓耻,消息無論從哪個(gè)節(jié)點(diǎn)進(jìn)入exchange茫舶,這個(gè)節(jié)點(diǎn)都會(huì)知道這個(gè)消息應(yīng)該路由到哪一個(gè)queue里面去。但是刹淌,一個(gè)consumer往往不會(huì)與所有的節(jié)點(diǎn)建立連接饶氏,這樣的話,未建立連接的節(jié)點(diǎn)需要將queue里面的消息轉(zhuǎn)發(fā)到那些建立了連接的節(jié)點(diǎn)以供消費(fèi)有勾。但是疹启,一個(gè)隊(duì)列里面的每一條消息最終都只會(huì)放到一個(gè)節(jié)點(diǎn)里面。
舉個(gè)例子蔼卡,假設(shè)我們有兩個(gè)節(jié)點(diǎn)(rabbit01喊崖,rabbit02),首先雇逞,我們在02節(jié)點(diǎn)創(chuàng)建一個(gè)exchange荤懂,那么01節(jié)點(diǎn)會(huì)自動(dòng)同步過去這個(gè)exchange的全部信息。之后我們在02節(jié)點(diǎn)創(chuàng)建了一個(gè)queue塘砸,那么對于集群中的01節(jié)點(diǎn)节仿,它將只知道集群中有這么一個(gè)隊(duì)列,并且知道它的結(jié)構(gòu)等元數(shù)據(jù)掉蔬。同時(shí)它將保存一個(gè)指向02節(jié)點(diǎn)這個(gè)queue的指針
廊宪。這樣就會(huì)出現(xiàn)一個(gè)場景:生產(chǎn)者生產(chǎn)了一條消息查近,并且發(fā)送到了01節(jié)點(diǎn),這時(shí)候01這個(gè)節(jié)點(diǎn)的exchange接收到了消息挤忙,對消息進(jìn)行路由霜威,但是由于當(dāng)前的consumer連接是位于02節(jié)點(diǎn)上面的,所以這時(shí)會(huì)利用上面所說的“指向02節(jié)點(diǎn)這個(gè)queue的指針”册烈,將消息路由到02節(jié)點(diǎn)上去戈泼。然后進(jìn)入queue,再交給consumer進(jìn)行消費(fèi)(這個(gè)過程中赏僧,邏輯上消息只有一份大猛,01節(jié)點(diǎn)和02節(jié)點(diǎn)的同一個(gè)queue不會(huì)同時(shí)擁有這一條消息)。如下圖所示:
很容易可以想到淀零,這種模式的優(yōu)點(diǎn)在于挽绩,每個(gè)節(jié)點(diǎn)不用關(guān)心所有的消息內(nèi)容,因此可以有更大的空間來緩存消息(因?yàn)橄⒅粫?huì)存一份)驾中。當(dāng)然缺點(diǎn)就是唉堪,如果一個(gè)節(jié)點(diǎn)掛掉,這個(gè)節(jié)點(diǎn)上所有未消費(fèi)的消息都無法從其他節(jié)點(diǎn)獲取肩民,一旦消息沒有做持久化唠亚,這些消息都會(huì)丟失。如果做了持久化持痰,也只能等這個(gè)節(jié)點(diǎn)重啟之后灶搜,才能再進(jìn)行消費(fèi)。
此外工窍,就如上面的例子割卖,如果consumer只與其中一個(gè)節(jié)點(diǎn)(假設(shè)是02節(jié)點(diǎn))建立了物理連接,那么對于集群來說患雏,這個(gè)queue消息的出口將總是在02節(jié)點(diǎn)上鹏溯,這就很容易產(chǎn)生性能瓶頸。并且纵苛,這種情況下02節(jié)點(diǎn)宕機(jī)剿涮,所有未確認(rèn)消息都會(huì)暫時(shí)無法獲取(因?yàn)槎际蔷彺嬖谶@個(gè)節(jié)點(diǎn)上的)攻人。一個(gè)處理的方案是取试,consumer盡可能地連接每一個(gè)節(jié)點(diǎn)(即對一個(gè)邏輯queue,在多個(gè)節(jié)點(diǎn)分別建立物理queue)怀吻,從這些節(jié)點(diǎn)獲取消息瞬浓,這樣一方面可以避免消息的大量路由,另一方面可以降低某一節(jié)點(diǎn)宕機(jī)帶來的影響蓬坡。
鏡像模式
鏡像模式與默認(rèn)模式的一個(gè)區(qū)別就是隊(duì)列內(nèi)消息的處理方式猿棉。
在鏡像模式下磅叛,每一個(gè)消息會(huì)在每一個(gè)節(jié)點(diǎn)主動(dòng)進(jìn)行拷貝,每個(gè)節(jié)點(diǎn)存儲(chǔ)一份(默認(rèn)模式下萨赁,只有當(dāng)consumer需要消費(fèi)數(shù)據(jù)時(shí)弊琴,才會(huì)對消息進(jìn)行拷貝)。如下圖所示:
同樣杖爽,我們可以看出敲董,對于鏡像模式而言,當(dāng)某一個(gè)節(jié)點(diǎn)掛掉的時(shí)候慰安,只要將鏈接切到另外一個(gè)節(jié)點(diǎn)下腋寨,就可以繼續(xù)進(jìn)行消息的正常消費(fèi),可靠性大為提升化焕。但是萄窜,因?yàn)镸Q內(nèi)部會(huì)存在大量的消息路由,所以MQ的整體性能會(huì)受到影響(內(nèi)部拷貝會(huì)占用大量的內(nèi)部網(wǎng)絡(luò)帶寬)撒桨,同時(shí)因?yàn)槊恳粋€(gè)節(jié)點(diǎn)都會(huì)保存所有的消息查刻,所以對消息的緩存能力會(huì)有一定的影響(主要是跟默認(rèn)模式進(jìn)行比較)。
一種使用思路
默認(rèn)模式和鏡像模式都互有優(yōu)劣元莫,其實(shí)可以考慮一種使用思路:大部分節(jié)點(diǎn)使用默認(rèn)模式赖阻,少部分(比如兩個(gè))節(jié)點(diǎn)使用鏡像模式。不過這部分還沒有深究踱蠢。
其他
流量控制
MQ的流控主要是指發(fā)生在消費(fèi)者消費(fèi)能力不足的情況下,對生產(chǎn)者進(jìn)行阻塞棋电。
兩種觸發(fā)條件:
- 基于內(nèi)存:如果MQ內(nèi)存使用量超過40%茎截,會(huì)拋出異常警告并阻塞所有生產(chǎn)鏈接(這個(gè)值可以調(diào)整)
- 基于磁盤:如果磁盤剩余容量少于1G,同樣會(huì)阻塞所有的生產(chǎn)者(這個(gè)值同樣可以調(diào)整)
特殊隊(duì)列/exchange
排他隊(duì)列
如果一個(gè)隊(duì)列聲明為了排他隊(duì)列赶盔,那么該隊(duì)列僅對首次聲明它的連接可見企锌,并且在斷開連接時(shí)自動(dòng)進(jìn)行刪除。
- 排他隊(duì)列是基于連接可見的于未,同一個(gè)連接的不同channel是可以訪問其他channel創(chuàng)建的排他隊(duì)列的
- 如果一個(gè)連接建立了排他隊(duì)列撕攒,那么是不允許其他連接建立同名的排他隊(duì)列的
- 即使這個(gè)隊(duì)列設(shè)置了持久化,一旦關(guān)閉連接(比如客戶端退出)烘浦,這個(gè)隊(duì)列也會(huì)自動(dòng)刪除抖坪。所以,一旦機(jī)器宕機(jī)闷叉,這個(gè)隊(duì)列是無法恢復(fù)的擦俐。同樣,排他隊(duì)列適用于一個(gè)客戶端進(jìn)行消息的發(fā)送和讀取的應(yīng)用場景握侧。
臨時(shí)隊(duì)列
隊(duì)列可以設(shè)置一個(gè)自動(dòng)刪除屬性蚯瞧,如果隊(duì)列沒有任何訂閱消費(fèi)者時(shí)嘿期,會(huì)自動(dòng)刪除的。
優(yōu)先級隊(duì)列
RabbitMQ本身并沒有實(shí)現(xiàn)優(yōu)先級隊(duì)列埋合,不過有插件可以進(jìn)行支持(好像3.5之后RabbitMQ自身已經(jīng)支持了备徐,沒有考證)。
Dead Letter Exchange
可以對一個(gè)queue設(shè)置一個(gè)Dead Letter Exchange屬性甚颂,當(dāng)消息超時(shí)或者消息被nack/reject并且設(shè)置requeue=false時(shí)坦喘,會(huì)進(jìn)入這個(gè)隊(duì)列。
因?yàn)镽abbitMQ沒有延時(shí)隊(duì)列和死信隊(duì)列的設(shè)置西设,所以需要借助于Dead Letter Exchange來實(shí)現(xiàn)瓣铣。RabbitMQ可以為queue或者消息添加一個(gè)超時(shí)屬性,當(dāng)超時(shí)未消費(fèi)的話贷揽,會(huì)進(jìn)行判斷棠笑,如果有設(shè)置Dead Letter Exchange,會(huì)進(jìn)入這個(gè)exchange里面禽绪,如果沒有設(shè)置,會(huì)直接被丟棄掉印屁。
Alternate Exchanges
生產(chǎn)者生產(chǎn)出來的消息循捺,進(jìn)入exchange之后雄人,找不到合適的queue時(shí)亚隙,會(huì)落到這個(gè)隊(duì)列中震桶。
項(xiàng)目中使用的RabbitMQ
我們用的是SpringBoot,核心代碼如下:
package com.rrc.async;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Configuration
public class MqConfig {
@Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost", 12345);
connectionFactory.setVirtualHost("vhostName");
connectionFactory.setUsername("userName");
connectionFactory.setPassword("password");
return connectionFactory;
}
@Bean
public AmqpAdmin amqpAdmin() {
return new RabbitAdmin(connectionFactory());
}
@Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
@Bean
public TopicExchange exchange() {
return new TopicExchange("exchangeName");
}
@Bean
public Queue testQueue() {
return new Queue("queueName");
}
@Bean
public Binding testBind() {
return BindingBuilder.bind(testQueue()).to(exchange()).with("routingKey");
}
@Bean
public SimpleMessageListenerContainer testContainer() {
SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer();
simpleMessageListenerContainer.setConnectionFactory(connectionFactory());
simpleMessageListenerContainer.addQueues(testQueue());
simpleMessageListenerContainer.setConcurrentConsumers(1);
simpleMessageListenerContainer.setMaxConcurrentConsumers(2);
// 這里沒有寫出來,testListener就是接收消息的bean(或者直接做成一個(gè)component)
simpleMessageListenerContainer.setMessageListener(testListener);
return simpleMessageListenerContainer;
}
}
問題
- 如何將一臺(tái)機(jī)器加入到MQ集群中鄙漏?
需要首先設(shè)置Erlang cookie杰捂,集群中的所有節(jié)點(diǎn)必須擁有相同的cookie翰灾。
cookie是一個(gè)字符串哮肚,最多可以有255個(gè)字符,通常存儲(chǔ)在本地文件中很钓,并且該文件必須設(shè)置成所有者訪問(400權(quán)限)香府。每個(gè)集群里面的所有節(jié)點(diǎn)必須擁有相同的cookie,文件位置:/var/lib/rabbitmq/.erlang.cookie
再之后執(zhí)行rabbitmqctl的指令即可:rabbitmqctl join_cluster rabbit@rabbit1
(上面的rabbit1是第一個(gè)節(jié)點(diǎn)的機(jī)器名码倦,其實(shí)rabbit@rabbit1就類似于work@10.171.12.3)
參考
https://blog.csdn.net/anzhsoft/article/details/19563091
https://blog.csdn.net/u013256816/article/details/77131753
http://www.rabbitmq.com/documentation.html
http://chyufly.github.io/blog/2016/04/10/rabbitmq-cluster/
https://www.cnblogs.com/sellsa/p/8056173.html