前置文章:
RabbitMQ-消息可靠性&延遲消息
零、本文綱要
一、MQ常見問題
二、消息堆積-惰性隊(duì)列
1、消息堆積問題
2、解決消息堆積方法
3、惰性隊(duì)列
三、高可用-MQ集群
1、集群分類
2、普通集群
3、鏡像集群
4、沖裁隊(duì)列
一、MQ常見問題
- ① 消息可靠性
確保發(fā)送的消息至少被消費(fèi)一次;
- ② 延遲消息
實(shí)現(xiàn)消息的延遲投遞;
- ③ 消息堆積
處理消息無法及時消費(fèi)的問題;
- ④ 高可用
避免單點(diǎn)MQ故障導(dǎo)致整體不可用;
二、消息堆積-惰性隊(duì)列
1、消息堆積問題
當(dāng)生產(chǎn)者發(fā)送消息的速度超過了消費(fèi)者處理消息的速度,就會導(dǎo)致隊(duì)列中的消息堆積,直到隊(duì)列存儲消息達(dá)到上限。最早接收到的消息,可能就會成為死信,會被丟棄,這就是消息堆積問題。
2、解決消息堆積方法
- ① 增加更多消費(fèi)者,提高消費(fèi)速度;
- ② 在消費(fèi)者內(nèi)開啟線程池加快消息處理速度;
- ③ 擴(kuò)大隊(duì)列容積,提高堆積上限。
3、惰性隊(duì)列
從RabbitMQ的3.6.0版本開始,就增加了Lazy Queues的概念,也就是惰性隊(duì)列。
- ① 惰性隊(duì)列特征
Ⅰ 接收到消息后直接存入磁盤而非內(nèi)存;
Ⅱ 消費(fèi)者要消費(fèi)消息時才會從磁盤中讀取并加載到內(nèi)存;
Ⅲ 支持數(shù)百萬條的消息存儲。
- ② 設(shè)置惰性隊(duì)列
要設(shè)置一個隊(duì)列為惰性隊(duì)列,只需要在聲明隊(duì)列時,指定x-queue-mode屬性為lazy即可。
Ⅰ 可以通過命令行將一個運(yùn)行中的隊(duì)列修改為惰性隊(duì)列,如下:
rabbitmqctl set_policy Lazy "^lazy-queue$" '{"queue-mode":"lazy"}' --apply-to queues
Ⅱ 用SpringAMQP聲明惰性隊(duì)列,如下:
@Bean注解的形式,如下:
@Bean
public Queue lazyQueue(){
return QueueBuilder
.durable("lazy.queue")
.lazy() // 開啟x-queue-mode為lazy
.build();
}
@RabbitListener注解的形式,如下:
@RabbitListener(queuesToDeclare = @Queue(
name = "lazy.queue",
durable = "true",
arguments = @Argument(name = "x-queue-mode", value = "lazy")
))
public void listenLazyQueue(String msg){
log.info("接收到lazy.queue的延遲消息:{}", msg);
}
- ④ 惰性隊(duì)列優(yōu)缺點(diǎn)
Ⅰ 優(yōu)點(diǎn)
基于磁盤存儲,消息上限高;
沒有間歇性的page-out,性能比較穩(wěn)定;
Ⅱ 缺點(diǎn)
基于磁盤存儲,消息時效性會降低;
性能受限于磁盤的IO。
三、高可用-MQ集群
官方文檔:Clustering Guide — RabbitMQ。
1、集群分類
- ① 普通集群
是一種分布式集群,將隊(duì)列分散到集群的各個節(jié)點(diǎn),從而提高整個集群的并發(fā)能力。
- ② 鏡像集群
是一種主從集群,普通集群的基礎(chǔ)上,添加了主從備份功能,提高集群的數(shù)據(jù)可用性。
注意:鏡像集群雖然支持主從,但主從同步并不是強(qiáng)一致的,某些情況下可能有數(shù)據(jù)丟失的風(fēng)險。
- ③ 仲裁隊(duì)列
在RabbitMQ的3.8版本以后推出的,底層采用Raft協(xié)議確保主從的數(shù)據(jù)一致性。
2、普通集群
- ① 普通集群特點(diǎn)
Ⅰ 會在集群的各個節(jié)點(diǎn)間共享部分?jǐn)?shù)據(jù),包括:交換機(jī)、隊(duì)列元信息。不包含隊(duì)列中的消息;
Ⅱ 當(dāng)訪問集群某節(jié)點(diǎn)時,如果隊(duì)列不在該節(jié)點(diǎn),會從數(shù)據(jù)所在節(jié)點(diǎn)傳遞到當(dāng)前節(jié)點(diǎn)并返回;
Ⅲ 隊(duì)列所在節(jié)點(diǎn)宕機(jī),隊(duì)列中的消息就會丟失。
- ② 模擬普通集群搭建
Ⅰ 獲取Cookie
RabbitMQ底層依賴于Erlang,而Erlang虛擬機(jī)就是一個面向分布式的語言,默認(rèn)就支持集群模式。集群模式中的每個RabbitMQ 節(jié)點(diǎn)使用 cookie 來確定它們是否被允許相互通信。
要使兩個節(jié)點(diǎn)能夠通信,它們必須具有相同的共享秘密,稱為Erlang cookie。cookie 只是一串最多 255 個字符的字母數(shù)字字符。
每個集群節(jié)點(diǎn)必須具有相同的 cookie。實(shí)例之間也需要它來相互通信。
首先獲取Cookie,指令如下:
docker exec -it mq cat /var/lib/rabbitmq/.erlang.cookie
其中YYNCLCJEKVNUFYQFPNZH這一串就是生成的Cookie,如下:
[root@localhost ~]# docker exec -it mq cat /var/lib/rabbitmq/.erlang.cookie
YYNCLCJEKVNUFYQFPNZH[root@localhost ~]#
Ⅱ 刪除現(xiàn)有mq容器
docker rm -f mq
Ⅲ 準(zhǔn)備rabbitmq.conf配置文件
此處選擇在tmp目錄下創(chuàng)建,如下:
cd /tmp
# 創(chuàng)建文件
touch rabbitmq.conf
配置文件內(nèi)容如下:
loopback_users.guest = false
listeners.tcp.default = 5672
cluster_formation.peer_discovery_backend = rabbit_peer_discovery_classic_config
cluster_formation.classic_config.nodes.1 = rabbit@mq1
cluster_formation.classic_config.nodes.2 = rabbit@mq2
cluster_formation.classic_config.nodes.3 = rabbit@mq3
Ⅳ 準(zhǔn)備Cookie記錄文件
# 創(chuàng)建cookie文件
touch .erlang.cookie
# 寫入cookie
echo "YYNCLCJEKVNUFYQFPNZH" > .erlang.cookie
# 修改cookie文件的權(quán)限
chmod 600 .erlang.cookie
Ⅴ 準(zhǔn)備集群目錄
# 創(chuàng)建目錄
mkdir mq1 mq2 mq3
Ⅵ 拷貝配置文件、Cookie文件到目錄
echo:用于字符串的輸出,輸出字符串到|后面;
-t:表示先打印命令,再執(zhí)行;
-n 1:表示執(zhí)行命令時用的args個數(shù)為1個。
# 批量拷貝rabbitmq.conf
echo mq1 mq2 mq3 | xargs -t -n 1 cp rabbitmq.conf
# 批量拷貝.erlang.cookie
echo mq1 mq2 mq3 | xargs -t -n 1 cp .erlang.cookie
Ⅶ 創(chuàng)建集群網(wǎng)絡(luò)
docker network create mq-net
Ⅷ 運(yùn)行容器
docker run -d --net mq-net \
-v ${PWD}/mq1/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf \
-v ${PWD}/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie \
-e RABBITMQ_DEFAULT_USER=test \
-e RABBITMQ_DEFAULT_PASS=123456 \
--name mq1 \
--hostname mq1 \
-p 8071:5672 \
-p 8081:15672 \
rabbitmq:3.8-management
docker run -d --net mq-net \
-v ${PWD}/mq2/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf \
-v ${PWD}/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie \
-e RABBITMQ_DEFAULT_USER=test \
-e RABBITMQ_DEFAULT_PASS=123456 \
--name mq2 \
--hostname mq2 \
-p 8072:5672 \
-p 8082:15672 \
rabbitmq:3.8-management
docker run -d --net mq-net \
-v ${PWD}/mq3/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf \
-v ${PWD}/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie \
-e RABBITMQ_DEFAULT_USER=test \
-e RABBITMQ_DEFAULT_PASS=123456 \
--name mq3 \
--hostname mq3 \
-p 8073:5672 \
-p 8083:15672 \
rabbitmq:3.8-management

- ③ 測試
集群中的節(jié)點(diǎn)標(biāo)示默認(rèn)都是:rabbit@[hostname]。
Ⅰ 往rabbit@mq1添加隊(duì)列

在mq2、mq3中也可以查看到該隊(duì)列,因?yàn)樵畔⒐蚕怼?/p>
Ⅱ 往simple.queue添加數(shù)據(jù)

在mq2、mq3中可以查看到消息,如下:

Ⅲ 讓mq1宕機(jī)
docker stop mq1
mq2、mq3無法讀取到數(shù)據(jù),因?yàn)橹还蚕碓畔?,沒有同步備份數(shù)據(jù),如下:

3、鏡像集群
鏡像集群官方文檔:Classic Queue Mirroring — RabbitMQ。
普通集群不具備高可用的特性,使用鏡像集群可以解決這個問題。
- ① 鏡像集群特征
Ⅰ 鏡像隊(duì)列結(jié)構(gòu)是一主多從(從就是鏡像);
Ⅱ 所有操作都是主節(jié)點(diǎn)完成,然后同步給鏡像節(jié)點(diǎn);
Ⅲ 主宕機(jī)后,鏡像節(jié)點(diǎn)會替代成新的主(如果在主從同步完成前,主就已經(jīng)宕機(jī),可能出現(xiàn)數(shù)據(jù)丟失);
Ⅳ 不具備負(fù)載均衡功能,因?yàn)樗胁僮鞫紩兄鞴?jié)點(diǎn)完成(但是不同隊(duì)列,其主節(jié)點(diǎn)可以不同,可以利用這個提高吞吐量)。
- ② 鏡像模式配置
| ha-mode | ha-params | 效果 |
|---|---|---|
| 準(zhǔn)確模式exactly | 隊(duì)列的副本量count | 集群中隊(duì)列副本(主服務(wù)器和鏡像服務(wù)器之和)的數(shù)量。count如果為1意味著單個副本:即隊(duì)列主節(jié)點(diǎn)。count值為2表示2個副本:1個隊(duì)列主和1個隊(duì)列鏡像。換句話說:count = 鏡像數(shù)量 + 1。如果群集中的節(jié)點(diǎn)數(shù)少于count,則該隊(duì)列將鏡像到所有節(jié)點(diǎn)。如果有集群總數(shù)大于count+1,并且包含鏡像的節(jié)點(diǎn)出現(xiàn)故障,則將在另一個節(jié)點(diǎn)上創(chuàng)建一個新的鏡像。 |
| all | (none) | 隊(duì)列在群集中的所有節(jié)點(diǎn)之間進(jìn)行鏡像。隊(duì)列將鏡像到任何新加入的節(jié)點(diǎn)。鏡像到所有節(jié)點(diǎn)將對所有群集節(jié)點(diǎn)施加額外的壓力,包括網(wǎng)絡(luò)I / O,磁盤I / O和磁盤空間使用情況。推薦使用exactly,設(shè)置副本數(shù)為(N / 2 +1)。 |
| nodes | node names | 指定隊(duì)列創(chuàng)建到哪些節(jié)點(diǎn),如果指定的節(jié)點(diǎn)全部不存在,則會出現(xiàn)異常。如果指定的節(jié)點(diǎn)在集群中存在,但是暫時不可用,會創(chuàng)建節(jié)點(diǎn)到當(dāng)前客戶端連接到的節(jié)點(diǎn)。 |
- ③ exactly模式(推薦)
rabbitmqctl set_policy ha-two "^two\." '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
-
rabbitmqctl set_policy:固定寫法 -
ha-two:策略名稱,自定義 -
"^two\.":匹配隊(duì)列的正則表達(dá)式,符合命名規(guī)則的隊(duì)列才生效,這里是任何以two.開頭的隊(duì)列名稱 -
'{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}': 策略內(nèi)容-
"ha-mode":"exactly":策略模式,此處是exactly模式,指定副本數(shù)量 -
"ha-params":2:策略參數(shù),這里是2,就是副本數(shù)量為2,1主1鏡像 -
"ha-sync-mode":"automatic":同步策略,默認(rèn)是manual,即新加入的鏡像節(jié)點(diǎn)不會同步舊的消息。如果設(shè)置為automatic,則新加入的鏡像節(jié)點(diǎn)會把主節(jié)點(diǎn)中所有消息都同步,會帶來額外的網(wǎng)絡(luò)開銷。
-
- ④ all模式
rabbitmqctl set_policy ha-all "^all\." '{"ha-mode":"all"}'
-
ha-all:策略名稱,自定義 -
"^all\.":匹配所有以all.開頭的隊(duì)列名 -
'{"ha-mode":"all"}':策略內(nèi)容-
"ha-mode":"all":策略模式,此處是all模式,即所有節(jié)點(diǎn)都會稱為鏡像節(jié)點(diǎn)
-
- ⑤ nodes模式
rabbitmqctl set_policy ha-nodes "^nodes\." '{"ha-mode":"nodes","ha-params":["rabbit@nodeA", "rabbit@nodeB"]}'
-
rabbitmqctl set_policy:固定寫法 -
ha-nodes:策略名稱,自定義 -
"^nodes\.":匹配隊(duì)列的正則表達(dá)式,符合命名規(guī)則的隊(duì)列才生效,這里是任何以nodes.開頭的隊(duì)列名稱 -
'{"ha-mode":"nodes","ha-params":["rabbit@nodeA", "rabbit@nodeB"]}': 策略內(nèi)容-
"ha-mode":"nodes":策略模式,此處是nodes模式 -
"ha-params":["rabbit@mq1", "rabbit@mq2"]:策略參數(shù),這里指定副本所在節(jié)點(diǎn)名稱
-
- ⑥ 測試exactly模式
Ⅰ 設(shè)置exactly模式
docker exec -it mq1 rabbitmqctl set_policy ha-two "^two\." '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
Ⅱ 創(chuàng)建隊(duì)列


Ⅲ 發(fā)送消息

Ⅳ 讓mq1宕機(jī)
docker stop mq1

注意:mq1恢復(fù)后,該隊(duì)列的主節(jié)點(diǎn)仍然為mq3。
4、沖裁隊(duì)列
- ① 仲裁隊(duì)列特征
Ⅰ 與鏡像隊(duì)列一樣,都是主從模式,支持主從數(shù)據(jù)同步;
Ⅱ 使用非常簡單,沒有復(fù)雜的配置;
Ⅲ 主從同步基于Raft協(xié)議,強(qiáng)一致。
注意:仲裁隊(duì)列是3.8版本以后才有的新功能。
- ② 添加仲裁隊(duì)列


+2表示有2個鏡像節(jié)點(diǎn),仲裁隊(duì)列默認(rèn)鏡像數(shù)為5,集群節(jié)點(diǎn)不足5則都是鏡像。
- ③ SpringAMQP創(chuàng)建仲裁隊(duì)列
@Bean注解配置
@Bean
public Queue quorumQueue(){
return QueueBuilder
.durable("quorum.queue") // 持久化
.quorum() // 仲裁隊(duì)列
.build();
}
修改配置文件
spring:
rabbitmq:
# host: 192.168.253.128 # rabbitMQ的ip地址
# port: 5672 # 端口
addresses: 192.168.253.128:8071,192.168.253.128:8072,192.168.253.128:8073
四、結(jié)尾
以上即為RabbitMQ-消息堆積&高可用的全部內(nèi)容,感謝閱讀。