在項目中想要 RabbitMQ 變得更加健壯,就要使得其變成高可用,今天咱們一塊兒來聊聊關於 RabbitMQ 集羣原理和部署流程node
1、介紹
在前幾篇文章中,咱們詳細的介紹了 RabbitMQ 的內部結構和使用,以及 SpringBoot 和 RabbitMQ 整合,都是基於單臺 RabbitMQ 進行使用的。c++
咱們知道在微服務流行的當下,一旦單臺服務器掛了,基本上就沒法提供高可用的服務了,所以爲了保證服務高可用,在生產環境上咱們一般的作法是搭建一個 RabbitMQ 集羣,即便某臺 RabbitMQ 故障了,其餘正常的 RabbitMQ 服務器依然可使用,應用程序的持續運行不會受到影響。web
2、集羣架構原理
在前幾篇文章中,咱們有介紹到 RabbitMQ 內部有各類基礎構件,包括隊列、交換器、綁定、虛擬主機等,他們組成了 AMQP 協議消息通訊的基礎,而這些構件以元數據的形式存在,它始終記錄在 RabbitMQ 內部,它們分別是:正則表達式
-
隊列元數據:隊列名稱和它們的屬性 -
交換器元數據:交換器名稱、類型和屬性 -
綁定元數據:一張簡單的表格展現瞭如何將消息路由到隊列 -
vhost 元數據:爲 vhost 內的隊列、交換器和綁定提供命名空間和安全屬性
這些元數據,其實本質是一張查詢表,裏面包括了交換器名稱和一個隊列的綁定列表,當你將消息發佈到交換器中,其實是將你所在的信道將消息上的路由鍵與交換器的綁定列表進行匹配,而後將消息路由出去。算法
有了這個機制,那麼在全部節點上傳遞交換器消息將簡單不少,而 RabbitMQ 所作的事情就是把交換器元數據拷貝到全部節點上,所以每一個節點上的每條信道均可以訪問完整的交換器。spring
若是消息生產者所鏈接的是節點 2 或者節點 3,此時隊列1的完整數據不在該兩個節點上,那麼在發送消息過程當中這兩個節點主要起了一個路由轉發做用,根據這兩個節點上的元數據轉發至節點1上,最終發送的消息仍是會存儲至節點1的隊列1上。sql
一樣,若是消息消費者所鏈接的節點2或者節點3,那這兩個節點也會做爲路由節點起到轉發做用,將會從節點1的隊列1中拉取消息進行消費。json
與常見的集羣主從架構模式不一樣的地方在於:RabbitMQ 集羣模式下,僅僅只是同步元數據,每一個隊列內容仍是在本身的服務器節點上。vim
這麼設計主要仍是基於集羣自己的性能和存儲空間上來考慮:後端
-
存儲空間:真正存放數據的地方是在隊列裏面,若是每一個集羣節點都擁有全部隊列的徹底數據拷貝,那麼每一個節點的存儲空間會很是大,集羣的消息積壓能力會很是弱。例如你如今存儲了 3G 隊列內容,那麼在另一個只有 1G 存儲空間的節點上,就會形成內存空間不足的狀況,也就是沒法經過集羣節點的擴容提升消息積壓能力。 -
性能:消息的發佈者須要將消息複製到每個集羣節點,每一條消息都會觸發磁盤活動,這會致使整個集羣內性能負載急劇拉昇。
既然每一個隊列內容仍是在本身的服務器節點上,一樣也會帶來新的問題,那就是若是隊列所在服務器掛了,那存在服務器上的隊列數據是否是所有都丟失了?
在單個節點上,RabbitMQ 存儲數據有兩種方案:
-
內存模式:這種模式會將數據存儲在內存當中,若是服務器忽然宕機重啓以後,那麼附加在該節點上的隊列和其關聯的綁定都會丟失,而且消費者能夠從新鏈接集羣並從新建立隊列; -
磁盤模式:這種模式會將數據存儲磁盤當中,若是服務器忽然宕機重啓,數據會自動恢復,該隊列又能夠進行傳輸數據了,而且在恢復故障磁盤節點以前,不能在其它節點上讓消費者從新連到集羣並從新建立隊列,若是消費者繼續在其它節點上聲明該隊列,會獲得一個 404 NOT_FOUND 錯誤,這樣確保了當故障節點恢復後加入集羣,該節點上的隊列消息不會丟失,也避免了隊列會在一個節點以上出現冗餘的問題。
在集羣中的每一個節點,要麼是內存節點,要麼是磁盤節點,若是是內存節點,會將全部的元數據信息僅存儲到內存中,而磁盤節點則不只會將全部元數據存儲到內存上, 還會將其持久化到磁盤。
在單節點 RabbitMQ 上,僅容許該節點是磁盤節點,這樣確保了節點發生故障或重啓節點以後,全部關於系統的配置與元數據信息都會從磁盤上恢復。
而在 RabbitMQ 集羣上,至少有一個磁盤節點,也就是在集羣環境中須要添加 2 臺及以上的磁盤節點,這樣其中一臺發生故障了,集羣仍然能夠保持運行。其它節點均設置爲內存節點,這樣會讓隊列和交換器聲明之類的操做會更加快速,元數據同步也會更加高效。
3、集羣部署
爲了和生產環境保持一致,咱們選用CentOS7
操做系統進行環境部署,分別建立 3 臺虛擬機。
# 3臺服務器的IP
197.168.24.206
197.168.24.233
197.168.24.234
放開防火牆限制,保證 3 臺服務器網絡均可以互通!
3.一、從新設置主機名
因爲 RabbitMQ 集羣鏈接是經過主機名來鏈接服務的,必須保證各個主機名之間能夠 ping 通,從新設置 3 臺服務器主機名,因此須要作如下操做:
# 修改節點1的主機名
hostname node1
# 修改節點2的主機名
hostname node2
# 修改節點3的主機名
hostname node3
編輯/etc/hosts
文件,添加到在三臺機器的/etc/hosts
中如下內容:
sudo vim /etc/hosts
添加內容以下:
197.168.24.206 node1
197.168.24.233 node2
197.168.24.234 node3
3.二、rabbitMQ安裝
RabbitMQ 基於 erlang 進行通訊,相比其它的軟件,安裝有些麻煩,不過本例採用rpm
方式安裝,任何新手均可以完成安裝,過程以下!
3.2.一、安裝前命令準備
輸入以下命令,完成安裝前的環境準備。
yum install lsof build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c++ kernel-devel m4 ncurses-devel tk tc xz wget vim
3.2.二、下載 RabbitMQ、erlang、socat 的安裝包
本次下載的是RabbitMQ-3.6.5
版本,採用rpm
一鍵安裝,適合新手直接上手。
先建立一個rabbitmq
目錄,本例的目錄路徑爲/usr/app/rabbitmq
,而後在目錄下執行以下命令,下載安裝包!
-
下載erlang
wget www.rabbitmq.com/releases/erlang/erlang-18.3-1.el7.centos.x86_64.rpm
-
下載socat
wget http://repo.iotti.biz/CentOS/7/x86_64/socat-1.7.3.2-5.el7.lux.x86_64.rpm
-
下載rabbitMQ
wget www.rabbitmq.com/releases/rabbitmq-server/v3.6.5/rabbitmq-server-3.6.5-1.noarch.rpm
最終目錄文件以下:
3.2.三、安裝軟件包
下載完以後,按順序依次安裝軟件包,這個很重要哦~
-
安裝erlang
rpm -ivh erlang-18.3-1.el7.centos.x86_64.rpm
-
安裝socat
rpm -ivh socat-1.7.3.2-5.el7.lux.x86_64.rpm
-
安裝rabbitmq
rpm -ivh rabbitmq-server-3.6.5-1.noarch.rpm
安裝完成以後,修改rabbitmq
的配置,默認配置文件在/usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin
目錄下。
vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/rabbit.app
修改loopback_users
節點的值!
分別從新命令rabbit
節點名稱
vim /etc/rabbitmq/rabbitmq-env.conf
在文件裏添加一行,以下配置!
NODENAME=rabbit@node1
其它兩個節點命令也相似,而後,再保存!經過以下命令,啓動服務便可!
# 啓動服務
rabbitmq-server start &
# 中止服務
rabbitmqctl stop
經過以下命令,查詢服務是否啓動成功!
lsof -i:5672
若是出現5672
已經被監聽,說明已經啓動成功!
3.2.四、啓動可視化的管控臺
輸入以下命令,啓動控制檯!
rabbitmq-plugins enable rabbitmq_management
用瀏覽器打開http://ip:15672
,這裏的ip
就是 CentOS 系統的 ip,結果以下:
帳號、密碼,默認爲guest
,若是出現沒法訪問,檢測防火牆是否開啓,若是開啓將其關閉便可!
登陸以後的監控平臺,界面以下:
3.三、複製 Erlang cookie
RabbitMQ 集羣環境下,元數據同步基於 cookie 共享方案實現。
在這裏將 node1 的 cookie 文件複製到 node2,因爲這個文件權限是 400 爲方便傳輸,先修改權限,非必須操做,因此須要先修改 node1 中的該文件權限爲 777
chmod 777 /var/lib/rabbitmq/.erlang.cookie
用 scp 拷貝到節點 2,節點 3 的操做也相似。
scp /var/lib/rabbitmq/.erlang.cookie node2:/var/lib/rabbitmq/
最後,將權限改回來
chmod 400 /var/lib/rabbitmq/.erlang.cookie
3.四、組成集羣
在節點 2 執行以下命令:
# 中止rabbitmq服務
rabbitmqctl stop_app
# 清空節點狀態
rabbitmqctl reset
# node2和node1構成集羣,node2必須能經過node1的主機名ping通
rabbitmqctl join_cluster rabbit@node1
# 開啓rabbitmq服務
rabbitmqctl start_app
節點 3 的操做也相似!
在任意一臺機上面查看集羣狀態:
rabbitmqctl cluster_status
-
第一行:表示當前節點信息 -
第二行:表示集羣中的節點成員,disc 表示這些都是磁盤節點 -
第三行:表示正在運行的節點成員
登陸可視化管控臺,能夠很清晰的看到,三個服務節點已經互相關聯起來了!
若是你想將某個節點移除集羣,以移除節點3爲例,能夠按照以下方式進行操做!
# 首先中止要移除的節點服務
rabbitmqctl stop
# 移除節點3
rabbitmqctl -n rabbit@node1 forget_cluster_node rabbit@node3
若是移除以後,沒法啓動 rabbitMQ,刪除已有 mnesia 信息!
rm -rf /var/lib/rabbitmq/mnesia
而後再次重啓服務便可!
3.五、設置內存節點
#加入時候設置節點爲內存節點(默認加入的爲磁盤節點)
rabbitmqctl join_cluster rabbit@node1 --ram
其中--ram
指的是做爲內存節點,若是不加,那就默認爲磁盤節點。
若是節點在集羣中已是磁盤節點了,經過如下命令能夠將節點改爲內存節點:
# 中止rabbitmq服務
rabbitmqctl stop_app
# 更改節點爲內存節點
rabbitmqctl change_cluster_node_type ram
# 開啓rabbitmq服務
rabbitmqctl start_app
3.六、鏡像隊列
上面咱們提到,在默認狀況下,隊列只會保存在其中一個節點上,當節點發生故障時,儘管全部元數據信息均可以從磁盤節點上將元數據恢復到本節點上,可是內存節點的隊列消息內容就不行了,這樣就會致使消息的丟失。
RabbitMQ 很早就意識到這個問題,在 2.6 之後的版本中增長了隊列冗餘選項:鏡像隊列。
所謂鏡像隊列,其實就是主隊列(master)依然是僅存在於一個節點上,經過關聯的 rabbitMQ 服務器,從主隊列同步消息到各個節點,也就是所謂的主從模式,將主隊列的消息進行備份處理。
若是主隊列沒有發生故障,那麼其工做流程跟普通隊列同樣,生產者和消費者不會感知其變化,當發佈消息時,依然是路由到主隊列中,而主隊列經過相似廣播的機制,將消息擴散同步至其他從隊列中,這就有點像 fanout 交換器同樣。而消費者依然是從主隊列中讀取消息。
一旦主隊列發生故障,集羣就會從最老的一個從隊列選舉爲新的主隊列,這也就實現了隊列的高可用了,但咱們切記不要濫用這個機制,在上面也說了,隊列的冗餘操做會致使不能經過擴展節點增長存儲空間,並且會形成性能瓶頸。
命令格式以下:
rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
參數介紹:
-p Vhost: 可選參數,針對指定vhost下的queue進行設置
Name: policy的名稱
Pattern: queue的匹配模式(正則表達式)
Definition: 鏡像定義,包括三個部分ha-mode, ha-params, ha-sync-mode
ha-mode: 指明鏡像隊列的模式,有效值爲 all/exactly/nodes
all: 表示在集羣中全部的節點上進行鏡像
exactly: 表示在指定個數的節點上進行鏡像,節點的個數由ha-params指定
nodes: 表示在指定的節點上進行鏡像,節點名稱經過ha-params指定
ha-params: ha-mode模式須要用到的參數
ha-sync-mode: 進行隊列中消息的同步方式,有效值爲automatic和manual
priority: 可選參數,policy的優先級
舉個例子,聲明名爲ha-all
的策略,它與名稱以ha
開頭的隊列相匹配,並將鏡像配置到集羣中的全部節點:
rabbitmqctl set_policy ha-all "^" '{"ha-mode":"all"}'
相似操做不少,具體使用能夠參考官方 api。
4、集羣的負載均衡
HAProxy 提供高可用性、負載均衡以及基於TCP和HTTP應用的代理,支持虛擬主機,它是免費、快速而且可靠的一種解決方案。根據官方數據,其最高極限支持10G的併發。HAProxy支持從4層至7層的網絡交換,即覆蓋全部的 TCP 協議。就是說,Haproxy 甚至還支持 Mysql 的均衡負載。爲了實現 RabbitMQ 集羣的軟負載均衡,這裏能夠選擇HAProxy。
4.一、HAProxy 安裝
HAProxy 的安裝也很簡單,單獨部署在一臺服務器上,經過以下命令便可安裝完成!
yum install haproxy
編輯 HAProxy 配置文件:
vim /etc/haproxy/haproxy.cfg
咱們只須要在文件末尾加上以下配置便可!
#綁定配置
listen rabbitmq_cluster
bind 0.0.0.0:5672
#配置TCP模式
mode tcp
#加權輪詢
balance roundrobin
#RabbitMQ集羣節點配置
server rmq_node1 197.168.24.206:5672 check inter 5000 rise 2 fall 3 weight 1
server rmq_node2 197.168.24.233:5672 check inter 5000 rise 2 fall 3 weight 1
server rmq_node3 197.168.24.234:5672 check inter 5000 rise 2 fall 3 weight 1
#haproxy監控頁面地址
listen monitor
bind 0.0.0.0:8100
mode http
option httplog
stats enable
stats uri /stats
stats refresh 5s
綁定配置參數說明:
-
bind
:這裏定義了客戶端鏈接鏈接 IP 地址和端口號,用於客戶端鏈接 -
balance roundrobin
:表示加權輪詢負載均衡算法
RabbitMQ 集羣節點配置說明:
-
server rmq_node1
:定義HAProxy內RabbitMQ服務的標識 -
197.168.24.206:5672
:標識了後端RabbitMQ的服務地址 -
check inter 5000
:表示每隔多少毫秒檢查RabbitMQ服務是否可用,示例參數值爲 5000 -
rise 2
:表示 RabbitMQ 服務在發生故障以後,須要多少次健康檢查才能被再次確承認用,示例參數值爲 2 -
fall 2
:表示須要經歷多少次失敗的健康檢查以後,HAProxy 纔會中止使用此RabbitMQ服務,示例參數值爲 2 -
weight 1
:表示權重比例,值越低,會優先進行數據分配,示例參數值爲 1
啓動 HAProxy:
/usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg
登陸http://ip:8100/stats
web 管理界面,便可進行監控查看!
5、Java 客戶端使用
若是是配置了 HAProxy 代理服務器,能夠直接使用 HAProxy 代理服務器地址便可!
//ConnectionFactory建立MQ的物理鏈接
connectionFactory = new ConnectionFactory();
connectionFactory.setHost("197.168.24.207"); //代理服務器地址
connectionFactory.setPort(5672); //代理服務器端口
connectionFactory.setUsername("admin"); //guest只能在本機進行訪問,經過代理服務器發送消息時須要從新創建用戶
connectionFactory.setPassword("admin"); //guest
connectionFactory.setVirtualHost("/"); //虛擬主機
若是沒有代理服務器,使用Spring
的CachingConnectionFactory
類進行配置。
以SpringBoot
項目爲例,配置文件以下:
spring.rabbitmq.addresses=197.168.24.206:5672,197.168.24.233:5672,197.168.24.234:5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
RabbitConfig
配置類以下:
@Configuration
public class RabbitConfig {
/**
* 初始化鏈接工廠
* @param addresses
* @param userName
* @param password
* @param vhost
* @return
*/
@Bean
ConnectionFactory connectionFactory(@Value("${spring.rabbitmq.addresses}") String addresses,
@Value("${spring.rabbitmq.username}") String userName,
@Value("${spring.rabbitmq.password}") String password,
@Value("${spring.rabbitmq.virtual-host}") String vhost) {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setAddresses(addresses);
connectionFactory.setUsername(userName);
connectionFactory.setPassword(password);
connectionFactory.setVirtualHost(vhost);
return connectionFactory;
}
/**
* 從新實例化 RabbitAdmin 操做類
* @param connectionFactory
* @return
*/
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
return new RabbitAdmin(connectionFactory);
}
/**
* 從新實例化 RabbitTemplate 操做類
* @param connectionFactory
* @return
*/
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
RabbitTemplate rabbitTemplate=new RabbitTemplate(connectionFactory);
//數據轉換爲json存入消息隊列
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
return rabbitTemplate;
}
}
6、總結
本文主要詳細介紹了 RabbitMQ 集羣的工做原理和如何搭建一個具有負載均衡能力的 RabbitMQ 集羣的方法。
限於筆者的才疏學淺,對本文內容可能還有理解不到位的地方,若有闡述不合理之處還望留言一塊兒探討。
7、參考
一、簡書 - 癲狂俠 - 消息中間件—RabbitMQ
二、後端進階 - zch - RabbitMQ集羣原理與部署
< END >
如果大家喜歡我們的文章,歡迎大家轉發,點擊在看讓更多的人看到。也歡迎大家熱愛技術和學習的朋友加入的我們的知識星球當中,我們共同成長,進步。
【推薦閱讀】
本文分享自微信公衆號 - Java極客技術(Javageektech)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。