RabbitMQ-消息堆積&高可用

前置文章:
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
集群容器啟動.png
  • ③ 測試

集群中的節(jié)點(diǎn)標(biāo)示默認(rèn)都是:rabbit@[hostname]。

Ⅰ 往rabbit@mq1添加隊(duì)列

往rabbit@mq1添加隊(duì)列.png

在mq2、mq3中也可以查看到該隊(duì)列,因?yàn)樵畔⒐蚕怼?/p>

Ⅱ 往simple.queue添加數(shù)據(jù)

往simple.queue添加數(shù)據(jù).png

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

在mq2、mq3中可以查看到消息.png

Ⅲ 讓mq1宕機(jī)

docker stop mq1

mq2、mq3無法讀取到數(shù)據(jù),因?yàn)橹还蚕碓畔?,沒有同步備份數(shù)據(jù),如下:

mq2、mq3無法讀取到數(shù)據(jù).png

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ì)列

創(chuàng)建隊(duì)列.png
mq2可以查看到該模式的隊(duì)列.png

Ⅲ 發(fā)送消息

發(fā)送消息查看消息.png

Ⅳ 讓mq1宕機(jī)

docker stop mq1
mq3晉升為該隊(duì)列主節(jié)點(diǎn).png

注意:mq1恢復(fù)后,該隊(duì)列的主節(jié)點(diǎn)仍然為mq3。

4、沖裁隊(duì)列

  • ① 仲裁隊(duì)列特征

Ⅰ 與鏡像隊(duì)列一樣,都是主從模式,支持主從數(shù)據(jù)同步;
Ⅱ 使用非常簡單,沒有復(fù)雜的配置;
Ⅲ 主從同步基于Raft協(xié)議,強(qiáng)一致。

注意:仲裁隊(duì)列是3.8版本以后才有的新功能。

  • ② 添加仲裁隊(duì)列
添加仲裁隊(duì)列.png
仲裁隊(duì)列鏡像節(jié)點(diǎn).png

+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)容,感謝閱讀。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容