這裏我們使用三個RabbitMQ節點:
192.168.7.41 rabbit1
192.168.7.42 rabbit2
192.168.7.43 rabbit3
開通埠(具體見官方文件):
firewall-cmd --zone=public --add-port=4369/tcp --permanent
firewall-cmd --zone=public --add-port=5672-5673/tcp --permanent
firewall-cmd --zone=public --add-port=15692/tcp --permanent
firewall-cmd --zone=public --add-port=15672/tcp --permanent
firewall-cmd --zone=public --add-port=25672/tcp --permanent
firewall-cmd --zone=public --add-port=35672-35682/tcp --permanent
firewall-cmd --reload
firewall-cmd --zone=public --list-port
安裝文件見:https://www.rabbitmq.com/install-rpm.html。
採用RPM包而不是Repo的安裝命令如下(以下的版本號可根據實際情況修改):
# 安裝ErLang
yum install https://bintray.com/rabbitmq-erlang/rpm/download_file\?file_path\=erlang%2F23%2Fel%2F7%2Fx86_64%2Ferlang-23.0.2-1.el7.x86_64.rpm
# 安裝RabbitMQ Server
yum install https://dl.bintray.com/rabbitmq/rpm/rabbitmq-server/v3.8.x/el/7/noarch/rabbitmq-server-3.8.5-1.el7.noarch.rpm
# 啓動服務
systemctl enable rabbitmq-server
systemctl start rabbitmq-server
安裝文件見:https://www.rabbitmq.com/management.html
安裝命令:rabbitmq-plugins enable rabbitmq-management
rabbitmqctl add_user admin admin
rabbitmqctl set_user_tags admin administrator
RabbitMQ可以通過三種方法來部署分佈式集羣系統,分別是:cluster
,federation
,shovel
。
問題說明: RabbitMQ要求在叢集中至少有一個磁碟節點,所有其他節點可以是記憶體節點,當節點加入或者離開叢集時,必須要將該變更通知到至少一個磁碟節點。如果叢集中唯一的一個磁碟節點崩潰的話,叢集仍然可以保持執行,但是無法進行其他操作(增刪改查),直到節點恢復。
**解決方案:**設定兩個磁碟節點,至少有一個是可用的,可以儲存元數據的更改。
Erlang Cookie是保證不同節點可以相互通訊的金鑰,要保證叢集中的不同節點相互通訊必須共用相同的Erlang Cookie。具體的目錄存放在/var/lib/rabbitmq/.erlang.cookie
。
說明: 這就要從rabbitmqctl命令的工作原理說起,RabbitMQ底層是通過Erlang架構來實現的,所以rabbitmqctl會啓動Erlang節點,並基於Erlang節點來使用Erlang系統連線RabbitMQ節點,在連線過程中需要正確的Erlang Cookie和節點名稱,Erlang節點通過交換Erlang Cookie以獲得認證。
以前對於HA有一種映象佇列(Classic Mirrored Queues),即將佇列內容複製到所有節點。3.8.0版本後,更加推薦Quorum佇列。
爲了保證這三臺機器的 Erlang cookie 相同,將rabbit1
上面的 Erlang cookie 檔案複製到另外兩臺機器上面。Erlang cookie 檔案的路徑是/var/lib/rabbitmq/.erlang.cookie
。
如果有$HOME/.erlang.cookie
,會優先使用,因此也可以把三個節點的$HOME/.erlang.cookie
的內容保持統一。
在rabbit1
檢視叢集狀態
> rabbitmqctl cluster_status root@rabbit1 15:50:14
Cluster status of node rabbit@rabbit1 ...
Basics
Cluster name: rabbit@rabbit1
Disk Nodes
rabbit@rabbit1
Running Nodes
rabbit@rabbit1
Versions
rabbit@rabbit1: RabbitMQ 3.8.5 on Erlang 23.0.2
...
其中,Cluster name: rabbit@rabbit1
,這是叢集名字,其他節點可以加入這個叢集中。
對其他兩個節點停止RabbitMQ服務,然後執行加入叢集命令:
# 停止RabbitMQ
rabbitmqctl stop_app
# 加入叢集
rabbitmqctl join_cluster rabbit@rabbit1
# 啓動RabbitMQ
rabbitmqctl start_app
在任意節點檢視叢集狀態:
> rabbitmqctl cluster_status root@rabbit3 16:01:55
Cluster status of node rabbit@rabbit3 ...
Basics
Cluster name: rabbit@rabbit1
Disk Nodes
rabbit@rabbit1
rabbit@rabbit2
rabbit@rabbit3
Running Nodes
rabbit@rabbit1
rabbit@rabbit2
rabbit@rabbit3
Versions
rabbit@rabbit1: RabbitMQ 3.8.5 on Erlang 23.0.2
rabbit@rabbit2: RabbitMQ 3.8.5 on Erlang 23.0.2
rabbit@rabbit3: RabbitMQ 3.8.5 on Erlang 23.0.2
可以看到三個節點已經構建成了一個叢集,但是有一個小問題,所有的節點都是磁碟節點,我們並不需要所有節點都是磁碟節點。假設這裏需要rabbit@rabbit1
爲磁碟節點,另外兩個都是記憶體節點,這樣我們在保證可用性的同時,還能提高叢集的整體效能。
下面 下麪將兩臺磁碟節點改成記憶體節點。
對於rabbit2
和rabbit3
進行如下操作:
rabbitmqctl stop_app
rabbitmqctl reset
這裏關鍵的命令是 rabbitmqctl reset
。reset 命令在節點爲單機狀態和是叢集的一部分時行爲有點不太一樣。
節點單機狀態時,reset 命令將清空節點的狀態,並將其恢復到空白狀態。當節點是叢集的一部分時,該命令也會和叢集中的磁碟節點通訊,告訴他們該節點正在離開叢集。
這很重要,不然,叢集會認爲該節點出了故障,並期望其最終能夠恢復回來,在該節點回來之前,叢集禁止新的節點加入。
對其他兩個節點停止RabbitMQ服務,然後執行加入叢集命令:
# 停止RabbitMQ
rabbitmqctl stop_app
# 加入叢集
rabbitmqctl join_cluster --ram rabbit@rabbit1
# 啓動RabbitMQ
rabbitmqctl start_app
首先安裝HAProxy:
yum install -y haproxy
設定HAProxy,/etc/haproxy/haproxy.cfg
:
global
log 127.0.0.1 local2
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 4000
user haproxy
group haproxy
daemon
stats socket /var/lib/haproxy/stats
#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
defaults
mode http
log global
option httplog
option dontlognull
option http-server-close
option redispatch
retries 3
timeout http-request 10s
timeout queue 1m
timeout connect 2s
timeout client 1m
timeout server 1m
timeout http-keep-alive 10s
timeout check 1s
maxconn 3000
#---------------------------------------------------------------------
# main frontend which proxys to the backends
#---------------------------------------------------------------------
frontend rabbitmq_15672
bind 0.0.0.0:15672
mode http
log global
option httplog
option forwardfor except 127.0.0.0/8
default_backend rabbitmq_management
#---------------------------------------------------------------------
# round robin balancing between the various backends
#---------------------------------------------------------------------
backend rabbitmq_management
balance roundrobin
server rabbit1 192.168.7.41:15672 check
server rabbit2 192.168.7.42:15672 check
server rabbit3 192.168.7.43:15672 check
listen rabbitcluster 0.0.0.0:5672
mode tcp
option tcplog
timeout client 3h
timeout server 3h
server rabbit1 192.168.7.41:5672 check fall 3 rise 2
server rabbit1 192.168.7.42:5672 check fall 3 rise 2
server rabbit1 192.168.7.43:5672 check fall 3 rise 2
利用keepalived
設定高可用。
安裝keepalived:
yum install keepalived
# 備份組態檔
cp /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.bak
編輯組態檔/etc/keepalived/keepalived.conf
# MASTER節點
global_defs {
router_id MYSQL_ROUTER # 各節點統一ID
vrrp_skip_check_adv_addr
vrrp_strict
vrrp_garp_interval 0
vrrp_gna_interval 0
}
vrrp_script check_mysqlrouter {
script "ps -C haproxy | grep haproxy" # 檢測HAProxy是否在執行
interval 2
weight 2
fall 2
}
vrrp_instance VI_1 {
state MASTER # 主節點
interface ens192 # VIP系結的網絡卡
virtual_router_id 33 # 各節點統一的虛擬ID
priority 102 # 數越高優先順序越高
advert_int 1 # 檢測間隔 1s
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.7.33 # VIP
}
track_script {
check_mysqlrouter # 檢測指令碼
}
}
# BACKUP節點(不同的設定)
state BACKUP # 備節點
priority 101 # 數值低於MASTER
設定防火牆:
firewall-cmd --add-rich-rule='rule protocol value="vrrp" accept' --permanent
firewall-cmd --reload
重新啓動keepalived:systemctl restart keepalived
,然後ip a
檢視VIP系結情況。
叢集實現高可用之後,佇列也要設定爲多節點才行,目前官方文件推薦設定爲Quorum Queue。佇列只能在用戶端建立的時候設定爲Quorum。
效果如下:
Queue
的時候需要指定x-queue-type
參數:Map<String, Object> args = new HashMap<>();
// // set the queue with a dead letter feature
args.put("x-queue-type", "queue");
return new Queue(MY_QUEUE_NAME, NON_DURABLE, false, false, args);