RabbitMQ 集羣高可用原理及實戰部署介紹

在項目中想要 RabbitMQ 變得更加健壯,就要使得其變成高可用,今天我們一起來聊聊關於 RabbitMQ 集羣原理和部署流程

一、介紹

在前幾篇文章中,我們詳細的介紹了 RabbitMQ 的內部結構和使用,以及 SpringBoot 和 RabbitMQ 整合,都是基於單臺 RabbitMQ 進行使用的。

我們知道在微服務流行的當下,一旦單臺服務器掛了,基本上就無法提供高可用的服務了,因此爲了保證服務高可用,在生產環境上我們通常的做法是搭建一個 RabbitMQ 集羣,即使某臺 RabbitMQ 故障了,其他正常的 RabbitMQ 服務器依然可以使用,應用程序的持續運行不會受到影響。

二、集羣架構原理

在前幾篇文章中,我們有介紹到 RabbitMQ 內部有各種基礎構件,包括隊列、交換器、綁定、虛擬主機等,他們組成了 AMQP 協議消息通信的基礎,而這些構件以元數據的形式存在,它始終記錄在 RabbitMQ 內部,它們分別是:

這些元數據,其實本質是一張查詢表,裏面包括了交換器名稱和一個隊列的綁定列表,當你將消息發佈到交換器中,實際上是將你所在的信道將消息上的路由鍵與交換器的綁定列表進行匹配,然後將消息路由出去。

消息路由表

有了這個機制,那麼在所有節點上傳遞交換器消息將簡單很多,而 RabbitMQ 所做的事情就是把交換器元數據拷貝到所有節點上,因此每個節點上的每條信道都可以訪問完整的交換器。

如果消息生產者所連接的是節點 2 或者節點 3,此時隊列 1 的完整數據不在該兩個節點上,那麼在發送消息過程中這兩個節點主要起了一個路由轉發作用,根據這兩個節點上的元數據轉發至節點 1 上,最終發送的消息還是會存儲至節點 1 的隊列 1 上。

同樣,如果消息消費者所連接的節點 2 或者節點 3,那這兩個節點也會作爲路由節點起到轉發作用,將會從節點 1 的隊列 1 中拉取消息進行消費。

與常見的集羣主從架構模式不同的地方在於RabbitMQ 集羣模式下,僅僅只是同步元數據,每個隊列內容還是在自己的服務器節點上

這麼設計主要還是基於集羣本身的性能和存儲空間上來考慮:

既然每個隊列內容還是在自己的服務器節點上,同樣也會帶來新的問題,那就是如果隊列所在服務器掛了,那存在服務器上的隊列數據是不是全部都丟失了

在單個節點上,RabbitMQ 存儲數據有兩種方案:

在集羣中的每個節點,要麼是內存節點,要麼是磁盤節點,如果是內存節點,會將所有的元數據信息僅存儲到內存中,而磁盤節點則不僅會將所有元數據存儲到內存上, 還會將其持久化到磁盤

在單節點 RabbitMQ 上,僅允許該節點是磁盤節點,這樣確保了節點發生故障或重啓節點之後,所有關於系統的配置與元數據信息都會從磁盤上恢復。

而在 RabbitMQ 集羣上,至少有一個磁盤節點,也就是在集羣環境中需要添加 2 臺及以上的磁盤節點,這樣其中一臺發生故障了,集羣仍然可以保持運行。其它節點均設置爲內存節點,這樣會讓隊列和交換器聲明之類的操作會更加快速,元數據同步也會更加高效。

三、集羣部署

爲了和生產環境保持一致,我們選用CentOS7操作系統進行環境部署,分別創建 3 臺虛擬機。

# 3臺服務器的IP
197.168.24.206
197.168.24.233
197.168.24.234

放開防火牆限制,保證 3 臺服務器網絡都可以互通!

3.1、重新設置主機名

由於 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.2、rabbitMQ 安裝

RabbitMQ 基於 erlang 進行通信,相比其它的軟件,安裝有些麻煩,不過本例採用rpm方式安裝,任何新手都可以完成安裝,過程如下!

3.2.1、安裝前命令準備

輸入如下命令,完成安裝前的環境準備。

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.2、下載 RabbitMQ、erlang、socat 的安裝包

本次下載的是RabbitMQ-3.6.5版本,採用rpm一鍵安裝,適合新手直接上手。

先創建一個rabbitmq目錄,本例的目錄路徑爲/usr/app/rabbitmq,然後在目錄下執行如下命令,下載安裝包!

wget www.rabbitmq.com/releases/erlang/erlang-18.3-1.el7.centos.x86_64.rpm
wget http://repo.iotti.biz/CentOS/7/x86_64/socat-1.7.3.2-5.el7.lux.x86_64.rpm
wget www.rabbitmq.com/releases/rabbitmq-server/v3.6.5/rabbitmq-server-3.6.5-1.noarch.rpm

最終目錄文件如下:

3.2.3、安裝軟件包

下載完之後,按順序依次安裝軟件包,這個很重要哦~

rpm -ivh erlang-18.3-1.el7.centos.x86_64.rpm
rpm -ivh socat-1.7.3.2-5.el7.lux.x86_64.rpm
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.4、啓動可視化的管控臺

輸入如下命令,啓動控制檯!

rabbitmq-plugins enable rabbitmq_management

用瀏覽器打開http://ip:15672,這裏的ip就是 CentOS 系統的 ip,結果如下:

賬號、密碼,默認爲guest,如果出現無法訪問,檢測防火牆是否開啓,如果開啓將其關閉即可!

登錄之後的監控平臺,界面如下:

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.4、組成集羣

在節點 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

登錄可視化管控臺,可以很清晰的看到,三個服務節點已經互相關聯起來了!

如果你想將某個節點移除集羣,以移除節點 3 爲例,可以按照如下方式進行操作!

# 首先停止要移除的節點服務
rabbitmqctl stop

# 移除節點3
rabbitmqctl -n rabbit@node1 forget_cluster_node rabbit@node3

如果移除之後,無法啓動 rabbitMQ,刪除已有 mnesia 信息!

rm -rf /var/lib/rabbitmq/mnesia

然後再次重啓服務即可!

3.5、設置內存節點

#加入時候設置節點爲內存節點(默認加入的爲磁盤節點)
rabbitmqctl join_cluster rabbit@node1 --ram

其中--ram指的是作爲內存節點,如果不加,那就默認爲磁盤節點。

如果節點在集羣中已經是磁盤節點了,通過以下命令可以將節點改成內存節點:

# 停止rabbitmq服務
rabbitmqctl stop_app

# 更改節點爲內存節點
rabbitmqctl change_cluster_node_type ram

# 開啓rabbitmq服務
rabbitmqctl start_app

3.6、鏡像隊列

上面我們提到,在默認情況下,隊列只會保存在其中一個節點上,當節點發生故障時,儘管所有元數據信息都可以從磁盤節點上將元數據恢復到本節點上,但是內存節點的隊列消息內容就不行了,這樣就會導致消息的丟失。

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。

四、集羣的負載均衡

HAProxy 提供高可用性、負載均衡以及基於 TCP 和 HTTP 應用的代理,支持虛擬主機,它是免費、快速並且可靠的一種解決方案。根據官方數據,其最高極限支持 10G 的併發。HAProxy 支持從 4 層至 7 層的網絡交換,即覆蓋所有的 TCP 協議。就是說,Haproxy 甚至還支持 Mysql 的均衡負載。爲了實現 RabbitMQ 集羣的軟負載均衡,這裏可以選擇 HAProxy。

4.1、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

綁定配置參數說明:

RabbitMQ 集羣節點配置說明:

啓動 HAProxy:

/usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg

登錄http://ip:8100/statsweb 管理界面,即可進行監控查看!

五、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("/");    //虛擬主機

如果沒有代理服務器,使用SpringCachingConnectionFactory類進行配置。

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;
    }

}

六、總結

本文主要詳細介紹了 RabbitMQ 集羣的工作原理和如何搭建一個具備負載均衡能力的 RabbitMQ 集羣的方法。

限於筆者的才疏學淺,對本文內容可能還有理解不到位的地方,如有闡述不合理之處還望留言一起探討。

七、參考

1、簡書 - 癲狂俠 - 消息中間件—RabbitMQ

2、後端進階 - zch - RabbitMQ 集羣原理與部署

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/gDIFiiznXYGsLGT7NYdBHg