HM-SpringCloud微服務系列12.4【MQ叢集】
1. 叢集分類
RabbitMQ的是基於Erlang語言編寫,而Erlang又是一個面向併發的語言,天然支援叢集模式。RabbitMQ的叢集有兩種模式:
-
普通叢集:是一種分散式叢集,將佇列分散到叢集的各個節點,從而提高整個叢集的併發能力。
- 普通模式叢集不進行資料同步,每個MQ都有自己的佇列、資料資訊(其它元資料資訊如交換機等會同步)。例如我們有2個MQ:mq1,和mq2,如果你的訊息在mq1,而你連線到了mq2,那麼mq2會去mq1拉取訊息,然後返回給你。如果mq1宕機,訊息就會丟失。
-
映象叢集:是一種主從叢集,普通叢集的基礎上,添加了主從備份功能,提高叢集的資料可用性。
- 與普通模式不同,佇列會在各個mq的映象節點之間同步,因此你連線到任何一個映象節點,均可獲取到訊息。而且如果一個節點宕機,並不會導致資料丟失。不過,這種方式增加了資料同步的頻寬消耗。
映象叢集雖然支援主從,但主從同步並不是強一致的,某些情況下可能有資料丟失的風險。因此在RabbitMQ的3.8版本以後,推出了新的功能:仲裁佇列來代替映象叢集,底層採用Raft協議確保主從的資料一致性。
2. 普通叢集
2.1 叢集結構和特徵
普通叢集,或者叫標準叢集(classic cluster)。
具備下列特徵:
- 會在叢集的各個節點間共享部分資料,包括:交換機、佇列元資訊。不包含佇列中的訊息。
- 當訪問叢集某節點時,如果佇列不在該節點,會從資料所在節點傳遞到當前節點並返回
- 佇列所在節點宕機,佇列中的訊息就會丟失
結構如圖:
2.2 部署
計劃部署3節點的mq叢集:
主機名 | 控制檯埠 | amqp通訊埠 |
---|---|---|
mq1 | 8081 ---> 15672 | 8071 ---> 5672 |
mq2 | 8082 ---> 15672 | 8072 ---> 5672 |
mq3 | 8083 ---> 15672 | 8073 ---> 5672 |
叢集中的節點標示預設都是:rabbit@[hostname]
,因此以上三個節點的名稱分別為:
- rabbit@mq1
- rabbit@mq2
- rabbit@mq3
2.2.1 獲取cookie
RabbitMQ底層依賴於Erlang,而Erlang虛擬機器就是一個面向分散式的語言,預設就支援叢集模式。
叢集模式中的每個RabbitMQ 節點使用 cookie 來確定它們是否被允許相互通訊。
要使兩個節點能夠通訊,它們必須具有相同的共享祕密,稱為Erlang cookie。
cookie 只是一串最多 255 個字元的字母數字字元。
每個叢集節點必須具有相同的 cookie。例項之間也需要它來相互通訊。
我們先在之前https://www.cnblogs.com/yppah/p/16244811.html啟動的mq2容器中獲取一個cookie值,作為叢集的cookie。
執行下面的命令:
docker exec -it mq2 cat /var/lib/rabbitmq/.erlang.cookie
可以看到cookie值如下:
GTLNTUUMPWVBGLSFOJVT
我們需要重新搭建叢集,接下來,停止並刪除當前的mq2容器,防止干擾:
docker rm -f mq2
然後,通過以下命令清理一下mq2的資料卷:
docker volume prune
此外,將https://www.cnblogs.com/yppah/p/15832492.html的mq也刪除掉
2.2.2 準備叢集配置
在/tmp目錄新建一個配置檔案 rabbitmq.conf:
cd /tmp
# 建立檔案
touch rabbitmq.conf
檔案內容如下:
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
在/tmp目錄再建立一個檔案,記錄cookie:
cd /tmp
# 建立cookie檔案
touch .erlang.cookie
# 寫入cookie(上一步中獲取到的)
echo "GTLNTUUMPWVBGLSFOJVT" > .erlang.cookie
# 修改cookie檔案的許可權(600:root使用者具有讀寫許可權,其他使用者無任何許可權)
chmod 600 .erlang.cookie
在/tmp下準備三個目錄mq1、mq2、mq3:
cd /tmp
# 建立目錄
mkdir mq1 mq2 mq3
然後拷貝rabbitmq.conf、cookie檔案到mq1、mq2、mq3:
# 進入/tmp
cd /tmp
# 拷貝
cp rabbitmq.conf mq1
cp rabbitmq.conf mq2
cp rabbitmq.conf mq3
cp .erlang.cookie mq1
cp .erlang.cookie mq2
cp .erlang.cookie mq3
2.2.3 啟動叢集
為了使三個docker容器可以互聯,首先建立一個網路
docker network create mq-net
docker volume create,依次執行命令:
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=haifei \
-e RABBITMQ_DEFAULT_PASS=123321 \
--name mq1 \
--hostname mq1 \
-p 8071:5672 \
-p 8081:15672 \
rabbitmq:3.8-management
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=haifei \
-e RABBITMQ_DEFAULT_PASS=123321 \
--name mq2 \
--hostname mq2 \
-p 8072:5672 \
-p 8082:15672 \
rabbitmq:3.8-management
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=haifei \
-e RABBITMQ_DEFAULT_PASS=123321 \
--name mq3 \
--hostname mq3 \
-p 8073:5672 \
-p 8083:15672 \
rabbitmq:3.8-management
埠專用報錯解決方案:https://blog.csdn.net/qq_47768542/article/details/114147974
PS:需要參照2.2.1 獲取cookie中方法刪掉剛剛圖1建立的mq1,再重新建立
再開啟三個視窗,分別通過命令docker logs -f mq1/2/3
看一下三個mq的啟動情況
本地瀏覽器分別訪問<10.193.193.141:8081/8082/8083>通過mq管理平臺檢視叢集
2.3 測試
2.3.1 資料共享測試
在mq1這個節點上新增一個佇列:
如圖,在mq2和mq3兩個控制檯也都能看到:
在mq1中點選這個佇列,進入管理頁面:
然後利用控制檯傳送一條訊息到這個佇列:
結果在mq2、mq3上都能看到這條訊息:
2.3.2 可用性測試
我們讓其中一臺節點mq1宕機:
docker stop mq1
然後登入mq2或mq3的控制檯,發現simple.queue也不可用了:
說明資料並沒有拷貝到mq2和mq3。
重新啟動mq1節點
3. 映象叢集
在剛剛的案例中,一旦建立佇列的主機宕機,佇列就會不可用。不具備高可用能力。
如果要解決這個問題,必須使用官方提供的映象叢集方案。
官方文件地址:https://www.rabbitmq.com/ha.html
PS:映象叢集實際上就是在傳統普通叢集基礎上做了一些配置實現的
3.1 叢集結構和特徵
映象叢集:本質是主從模式。
具備下面的特徵:
- 交換機、佇列、佇列中的訊息會在各個mq的映象節點之間同步備份。
- 建立佇列的節點被稱為該佇列的主節點,備份到的其它節點叫做該佇列的映象節點。
- 一個佇列的主節點可能是另一個佇列的映象節點
- 所有操作都是主節點完成,然後同步給映象節點
- 主宕機後,映象節點會替代成新的主
結構如圖:
3.2 映象模式的特徵
預設情況下,佇列只儲存在建立該佇列的節點上。而映象模式下,建立佇列的節點被稱為該佇列的主節點,佇列還會拷貝到叢集中的其它節點,也叫做該佇列的映象節點。
但是,不同佇列可以在叢集中的任意節點上建立,因此不同佇列的主節點可以不同。甚至,一個佇列的主節點可能是另一個佇列的映象節點。
使用者傳送給佇列的一切請求,例如傳送訊息、訊息回執預設都會在主節點完成,如果是從節點接收到請求,也會路由到主節點去完成。映象節點僅僅起到備份資料作用。
當主節點接收到消費者的ACK時,所有映象都會刪除節點中的資料。
總結如下:
- 映象佇列結構是一主多從(從就是映象)
- 所有操作都是主節點完成,然後同步給映象節點
- 主宕機後,映象節點會替代成新的主(如果在主從同步完成前,主就已經宕機,可能出現數據丟失)
- 不具備負載均衡功能,因為所有操作都會有主節點完成(但是不同佇列,其主節點可以不同,可以利用這個提高吞吐量)
3.3 映象模式的配置
映象模式的配置有3種模式:
ha-mode | ha-params | 效果 |
---|---|---|
準確模式exactly | 佇列的副本量count | 叢集中佇列副本(主伺服器和映象伺服器之和)的數量。count如果為1意味著單個副本:即佇列主節點。count值為2表示2個副本:1個佇列主和1個佇列映象。換句話說:count = 映象數量 + 1。如果群集中的節點數少於count,則該佇列將映象到所有節點。如果有叢集總數大於count+1,並且包含映象的節點出現故障,則將在另一個節點上建立一個新的映象。 |
all | (none) | 佇列在群集中的所有節點之間進行映象。佇列將映象到任何新加入的節點。映象到所有節點將對所有群集節點施加額外的壓力,包括網路I / O,磁碟I / O和磁碟空間使用情況。推薦使用exactly,設定副本數為(N / 2 +1)。 |
nodes | node names | 指定佇列建立到哪些節點,如果指定的節點全部不存在,則會出現異常。如果指定的節點在叢集中存在,但是暫時不可用,會建立節點到當前客戶端連線到的節點。 |
這裡我們以rabbitmqctl命令作為案例來講解配置語法。
語法示例如下
3.3.1 exactly模式
rabbitmqctl set_policy ha-two "^two\." '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
-
rabbitmqctl set_policy
:固定寫法 -
ha-two
:策略名稱,自定義 -
"^two\."
:匹配佇列的正則表示式,符合命名規則的佇列才生效,這裡是任何以two.
開頭的佇列名稱 -
'{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
: 策略內容-
"ha-mode":"exactly"
:策略模式,此處是exactly模式,指定副本數量 -
"ha-params":2
:策略引數,這裡是2,就是副本數量為2,1主1映象 -
"ha-sync-mode":"automatic"
:同步策略,預設是manual,即新加入的映象節點不會同步舊的訊息。如果設定為automatic,則新加入的映象節點會把主節點中所有訊息都同步,會帶來額外的網路開銷
-
3.3.2 all模式
rabbitmqctl set_policy ha-all "^all\." '{"ha-mode":"all"}'
-
ha-all
:策略名稱,自定義 -
"^all\."
:匹配所有以all.
開頭的佇列名 -
'{"ha-mode":"all"}'
:策略內容-
"ha-mode":"all"
:策略模式,此處是all模式,即所有節點都會稱為映象節點
-
3.3.3 nodes模式
rabbitmqctl set_policy ha-nodes "^nodes\." '{"ha-mode":"nodes","ha-params":["rabbit@nodeA", "rabbit@nodeB"]}'
-
rabbitmqctl set_policy
:固定寫法 -
ha-nodes
:策略名稱,自定義 -
"^nodes\."
:匹配佇列的正則表示式,符合命名規則的佇列才生效,這裡是任何以nodes.
開頭的佇列名稱 -
'{"ha-mode":"nodes","ha-params":["rabbit@nodeA", "rabbit@nodeB"]}'
: 策略內容-
"ha-mode":"nodes"
:策略模式,此處是nodes模式 -
"ha-params":["rabbit@mq1", "rabbit@mq2"]
:策略引數,這裡指定副本所在節點名稱
-
3.4 測試
我們使用exactly模式的映象,因為叢集節點數量為3,因此映象數量就設定為2.
執行下面的命令進入mq1容器內部:
docker exec -it mq1 bash
然後配置映象模式:
rabbitmqctl set_policy ha-two "^two\." '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
下面,在mq1上建立一個新的佇列:
在任意一個mq控制檯檢視佇列:
3.4.1 資料共享測試
在mq1中給two.queue傳送一條訊息:
然後在mq1、mq2、mq3的任意控制檯檢視訊息:
3.4.2 高可用測試
現在,我們讓two.queue的主節點mq1宕機:
docker stop mq1
檢視叢集狀態:
檢視佇列狀態:
發現依然是健康的!並且其主節點切換到了rabbit@mq2上
現在,再來重啟mq1
可以發現,two.queue的主機仍是mq2,已與mq1無關
4. 仲裁佇列
4.1 叢集結構和特徵
仲裁佇列:仲裁佇列是3.8版本以後才有的新功能,用來替代映象佇列。
具備下列特徵:
- 與映象佇列一樣,都是主從模式,支援主從資料同步
- 使用非常簡單,沒有複雜的配置
- 主從同步基於Raft協議,強一致
4.2 控制檯手動新增仲裁佇列
在任意控制檯新增一個佇列,一定要選擇佇列型別為Quorum型別。
在任意控制檯檢視佇列:
可以看到,仲裁佇列的 + 2字樣。代表這個佇列有2個映象節點。
因為仲裁佇列預設的映象數為5。如果你的叢集有7個節點,那麼映象數肯定是5;而我們叢集只有3個節點,因此映象數量就是3。
4.3 Java處理仲裁佇列
4.3.1 SpringAMQP連線MQ叢集
注意,yaml這裡用address來代替host、port方式
spring:
rabbitmq:
addresses: 10.193.193.141:8071, 10.193.193.141:8072, 10.193.193.141:8073
username: haifei
password: 123321
virtual-host: /
4.3.2 SpringAMQP建立仲裁佇列
@Bean
public Queue quorumQueue() {
return QueueBuilder
.durable("quorum.queue") // 持久化
.quorum() // 仲裁佇列
.build();
}
4.3.3 測試
考慮到目前為mq叢集模式,consumer服務config包下其餘配置類都是針對此前單機課程的配置,
倘若當前直接啟動consumer服務,難免會報一堆錯,
因此,現將除QuorumConfig類之外的其餘配置類中的@Configuration註解都先註釋掉
並且,令SpringRabbitListener類中只保留istenSimpleQueue(),其餘方法先註釋掉
package cn.itcast.mq.listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class SpringRabbitListener {
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueue(String msg) {
System.out.println("消費者接收到simple.queue的訊息:【" + msg + "】");
}
/*@RabbitListener(queues = "simple.queue")
public void listenSimpleQueue(String msg) {
log.debug("消費者接收到simple.queue的訊息:【" + msg + "】");
System.out.println(1/0); //模擬異常:手動錯誤
log.info("消費者處理訊息成功!");
}*/
/*@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "dl.queue", durable = "true"),
exchange = @Exchange(name = "dl.direct"),
key = "dl"
))
public void listenDlQueue(String msg) {
log.debug("消費者接收到dl.queue的延遲訊息:{}", msg);
}*/
/*@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "delay.queue", durable = "true"),
exchange = @Exchange(name = "delay.direct", delayed = "true"),
key = "delay"
))
public void listenDelayQueue(String msg) {
log.debug("消費者接收到delay.queue的延遲訊息:{}", msg);
}*/
}
啟動consumer服務
PS:建立佇列是如上操作;傳送訊息跟此前學的一樣,再次不再演示
4.4 叢集擴容(擴充套件|未講)
4.4.1 加入叢集
1)啟動一個新的MQ容器:
docker run -d --net mq-net \
-v ${PWD}/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie \
-e RABBITMQ_DEFAULT_USER=itcast \
-e RABBITMQ_DEFAULT_PASS=123321 \
--name mq4 \
--hostname mq5 \
-p 8074:15672 \
-p 8084:15672 \
rabbitmq:3.8-management
2)進入容器控制檯:
docker exec -it mq4 bash
3)停止mq程序
rabbitmqctl stop_app
4)重置RabbitMQ中的資料:
rabbitmqctl reset
5)加入mq1:
rabbitmqctl join_cluster rabbit@mq1
6)再次啟動mq程序
rabbitmqctl start_app
4.4.2 增加仲裁佇列副本
我們先檢視下quorum.queue這個佇列目前的副本情況,進入mq1容器:
docker exec -it mq1 bash
執行命令:
rabbitmq-queues quorum_status "quorum.queue"
結果:
現在,我們讓mq4也加入進來:
rabbitmq-queues add_member "quorum.queue" "rabbit@mq4"
結果:
再次檢視:
rabbitmq-queues quorum_status "quorum.queue"
檢視控制檯,發現quorum.queue的映象數量也從原來的 +2 變成了 +3: