RabbitMQ集羣和高可用方案

RabbitMQ高可用集羣方案

RabbitMQ的Cluster模式分爲兩種:

  • 普通模式

  • 鏡像模式

Cluster普通模式:

元數據包含以下內容:

  • 隊列元數據:隊列的名稱及屬性

  • 交換器:交換器的名稱及屬性

  • 綁定關係元數據:交換器與隊列或者交換器與交換器

  • vhost元數據:爲vhost內的隊列,交換器和綁定提供命名空間及安全屬性之間的綁定關係

Cluster多機多節點部署:多機多節點是指在每臺機器中部署一個RabbitMQ服務節點,進而由多個機器組成一個RabbitMQ集羣

Cluster單機多節點部署:由於某些因素的限制,有時候不得不在單臺物理機器上去創建一個多RabbitMQ服務節點的集羣。或者只想要實驗性的驗證集羣的某些特性,也不需要浪費過多的物理機器去實現。需要爲每個RabbitMQ服務節點設置不同的端口號和節點名稱來啓動相應的服務

Cluster鏡像模式:

鏡像模式的集羣是在普通模式的基礎上,通過policy來實現,使用鏡像模式可以實現RabbitMQ的高可用方案

ha-sync-mode
隊列中消息的同步方式,有效值爲automatic和manual,默認爲automatic

RabbitMQ集羣搭建

  1. 環境準備

準備3臺機器,並在這三臺機器上安裝RabbitMQ

192.168.0.22 node1
192.168.0.23 node2
192.168.0.24 node3
  1. 修改配置文件

依次修改對應主機的hostname

hostname node1
hostname node2
hostname node3

依次修改主機的/etc/hosts文件,添加以下內容

192.168.0.22 node1
192.168.0.23 node2
192.168.0.24 node3

將node1節點上的 /var/lib/rabbitmq/.erlang.cookie 文件複製到其他節點(Erlang語言要求必須有相同的cookie才能進行集羣通信)

scp /var/lib/rabbitmq/.erlang.cookie root@node2:/var/lib/rabbitmq/
scp /var/lib/rabbitmq/.erlang.cookie root@node3:/var/lib/rabbitmq/
  1. 添加防火牆端口

給每臺機器的防火牆添加端口

firewall-cmd --zone=public --add-port=4369/tcp --permanent
firewall-cmd --zone=public --add-port=5672/tcp --permanent
firewall-cmd --zone=public --add-port=25672/tcp --permanent
firewall-cmd --zone=public --add-port=15672/tcp --permanent

重啓防火牆

firewall-cmd --reload
  1. 啓動RabbitMQ集羣

啓動每臺機器的RabbitMQ

systemctl start rabbitmq-server

操作節點node2,將node2加入到集羣

rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster rabbit@node1 --ram
rabbitmqctl start_app

查看集羣的狀態

rabbitmqctl cluster_status

在node3節點上重複上述命令,將node3加入到集羣。完成後查看集羣狀態

RabbitMQ Web端查看集羣狀態

同時在Web端還可以看到每個節點的詳細信息,如內存情況,IO情況,數據的存儲等等

鏡像隊列模式集羣

鏡像隊列屬於RabbitMQ 的高可用方案,見:https://www.rabbitmq.com/ha.html#mirroring-arguments
通過前面的步驟搭建的集羣屬於普通模式集羣,是通過共享元數據實現集羣
開啓鏡像隊列模式需要在管理頁面添加策略,添加方式:
進入管理頁面 -> Admin -> Policies(在頁面右側)-> Add / update a policy
在表單中填入:

參數說明:
name: 策略名稱,如果使用已有的名稱,保存後將會修改原來的信息
Apply to:策略應用到什麼對象上
Pattern:策略應用到對象時,對象名稱的匹配規則(正則表達式)
Priority:優先級,數值越大,優先級越高,相同優先級取最後一個
Definition:策略定義的內容,對於鏡像隊列的配置來說,只需要包含3個部分: ha-modeha-paramsha-sync-mode。其中,ha-sync-mode是同步的方式,自動還是手動,默認是自動。ha-modeha-params 組合使用。組合方式如下:

鏡像隊列模式相比較普通模式,鏡像模式會佔用更多的帶寬來進行同步,所以鏡像隊列的吞吐量會低於普通模式。但普通模式不能實現高可用,某個節點掛了後,這個節點上的消息將無法被消費,需要等待節點啓動後才能被消費。

集羣測試代碼示例:

Producer示例:

import com.rabbitmq.client.*;import java.io.IOException;import java.time.Instant;import java.util.concurrent.TimeUnit;public class Producer {  public static void main(String[] args) {    // 創建連接工廠
ConnectionFactory connectionFactory = new ConnectionFactory(); // 設置連接屬性
connectionFactory.setUsername("test-user");
connectionFactory.setPassword("test-user");
connectionFactory.setVirtualHost("v1"); // 設置每個節點的鏈接地址和端口
Address[] addresses = new Address[] { new Address("192.168.0.22", 5672), new Address("192.168.0.23", 5672), new Address("192.168.0.24", 5672)
};

Connection connection = null;
Channel channel = null; try { // 開啓/關閉連接自動恢復,默認是開啓狀態
connectionFactory.setAutomaticRecoveryEnabled(true); // 設置每100毫秒嘗試恢復一次,默認是5秒
connectionFactory.setNetworkRecoveryInterval(100);

connectionFactory.setTopologyRecoveryEnabled(false); // 從使用連接集合裏面的地址獲取連接
connection = connectionFactory.newConnection(addresses, "Producer"); // 添加重連監聽器
((Recoverable) connection)
.addRecoveryListener( new RecoveryListener() { // 重連成功後的回調
public void handleRecovery(Recoverable recoverable) {
System.out.println(Instant.now().toString() + " 已重新建立連接");
} // 開始重連時的回調
public void handleRecoveryStarted(Recoverable recoverable) {
System.out.println(Instant.now().toString() + " 開始嘗試重連");
}
}); // 從鏈接中創建通道
channel = connection.createChannel(); // 聲明隊列,如果隊列不存在,會創建
// RabbitMQ 不允許聲明兩個隊列名相同,屬性不同的隊列,否則會報錯
channel.queueDeclare("queue-test", true, false, false, null); for (int i = 0; i < 100; i++) {
String message = "Hello Rabbit " + i; try { // 發送消息
channel.basicPublish("", "queue-test", null, message.getBytes());

} catch (Exception e) { // 可能連接已關閉,等待重連
System.out.println("消息 " + message + " 發送失敗!");
i--;
TimeUnit.SECONDS.sleep(2); continue;
}

System.out.println("消息" + i + "已發送!");
TimeUnit.SECONDS.sleep(2);
}

} catch (Exception e) {
e.printStackTrace();
} finally { // 關閉通道
if (channel != null && channel.isOpen()) { try {
channel.close();
} catch (Exception e) {
e.printStackTrace();
}
} // 關閉連接
if (connection != null && connection.isOpen()) { try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

Consumer示例:

import com.rabbitmq.client.*;import java.io.IOException;import java.time.Instant;public class Consumer {  public static void main(String[] args) {    // 創建連接工廠
ConnectionFactory connectionFactory = new ConnectionFactory(); // 設置連接屬性
connectionFactory.setUsername("test-user");
connectionFactory.setPassword("test-user");
connectionFactory.setVirtualHost("v1"); // 設置每個節點的鏈接地址和端口
Address[] addresses = new Address[] { new Address("192.168.0.22", 5672), new Address("192.168.0.23", 5672), new Address("192.168.0.24", 5672)
};

Connection connection = null;
Channel channel = null; try { // 開啓/關閉連接自動恢復,默認是開啓狀態
connectionFactory.setAutomaticRecoveryEnabled(true); // 設置每100毫秒嘗試恢復一次,默認是5秒
connectionFactory.setNetworkRecoveryInterval(100); // 從連接工廠獲取連接
connection = connectionFactory.newConnection(addresses, "Consumer"); // 添加重連監聽器
((Recoverable) connection)
.addRecoveryListener( new RecoveryListener() { // 重連成功後的回調
public void handleRecovery(Recoverable recoverable) {
System.out.println(Instant.now().toString() + " 已重新建立連接");
} // 開始重連時的回調
public void handleRecoveryStarted(Recoverable recoverable) {
System.out.println(Instant.now().toString() + " 開始嘗試重連");
}
}); // 從鏈接中創建通道
channel = connection.createChannel(); // 聲明隊列,如果隊列不存在,會創建
// RabbitMQ 不允許聲明兩個隊列名相同,屬性不同的隊列,否則會報錯
channel.queueDeclare("queue-test", true, false, false, null); // 定義收到消息後的回調
final Channel finalChannel = channel;
DeliverCallback deliverCallback = new DeliverCallback() {

@Override public void handle(String consumerTag, Delivery message) throws IOException {
System.out.println("收到消息:" + new String(message.getBody(), "UTF-8"));
finalChannel.basicAck(message.getEnvelope().getDeliveryTag(), false);
}
}; // 監聽隊列
channel.basicConsume( "queue-test", false,
deliverCallback, new CancelCallback() {
@Override public void handle(String consumerTag) throws IOException {}
});

System.out.println("開始接收消息");
System.in.read();

} catch (Exception e) {
e.printStackTrace();
} finally { // 關閉通道
if (channel != null && channel.isOpen()) { try {
channel.close();
} catch (Exception e) {
e.printStackTrace();
}
} // 關閉連接
if (connection != null && connection.isOpen()) { try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

RabbitMQ常用管理命令

rabbitmqctl status
查看節點狀態

rabbitmqctl stop [pid_file]
停止運行的RabbitMQ的Erlang虛擬機和RabbitMQ服務應用
如果指定了pid_file,還需要等待指定進程的結束。pid_file是通過調用rabbitmq-server命令啓動RabbitMQ服務時創建的,默認情況下存放於Mnesia目錄中。
如果使用rabbitmq-server -detach這個帶有-detach後綴的命令來啓動RabbitMQ服務則不會生成pid_file文件。

rabbitmqctl stop_app
停止RabbitMQ服務應用,但是Erlang虛擬機還是處於運行狀態
此命令的執行優先於其他管理操作(這些操作需要先停止RabbitMQ應用,如rabbitmqctl reset)

rabbitmqctl start_app
啓動RabbitMQ應用,此命令典型的用途就是執行了其他管理操作之後,重新啓動之前停止的RabbitMQ應用。

rabbitmqctl reset
將RabbitMQ節點重置還原到最初狀態
包括從原來的集羣中刪除此節點,從管理數據庫中刪除所有的配置數據,如已配置的用戶,vhost等,以及刪除所有的持久化數據
執行rabbitmqctl reset 命令前必須停止RabbitMQ應用

rabbitmqctl force_reset
強制將RabbitMQ節點重置還原到最初狀態。此命令不論當前管理數據庫的狀態和集羣配置是什麼,都會無條件的重置節點,只能在數據庫或集羣配置已損壞的情況下使用

rabbitmqctl [-n nodename] join_cluster {cluster_node} [—ram]
將節點加入指定的集羣中。在這個命令執行前需要停止RabbitMQ應用並重置節點。
-n nodename:指定需要操作的目標節點,如rabbit@node1
cluster_node:需要加入的集羣節點名,如rabbit@node2
—ram:集羣節點類型,有ram,disc兩種,默認爲disc

  • ram 內存節點,所有元數據都存儲在內存中

  • disc 磁盤節點,所有元數據都存儲在磁盤中

rabbitmqctl cluster_status
查看集羣狀態

rabbitmqctl change_cluster_node_type {disc|ram}
修改集羣節點的類型,使用此命令前要停止RabbitMQ應用

rabbitmqctl forget_cluster_node [—offline]
將節點從集羣中刪除,允許離線執行

rabbitmqctl update_cluster_nodes {clusternode}
在集羣中的節點應用啓動前諮詢clusternode節點的最新信息,並更新相應的集羣信息。這個和join_cluster不同,它不加入集羣

rabbitmqctl force_boot
確保節點可以啓動,即使它不是最後一個關閉的節點

rabbitmqctl set_cluster_name {name}
設置集羣名稱。集羣名稱在客戶端連接時會通報給客戶端
集羣名稱默認是集羣中第一個節點的名稱,通過這個命令可以重新設置

Federation插件

Federation插件的設計目標是使RabbitMQ在不同Broker節點之間進行消息傳遞而無需建立集羣,該功能在以下場景下非常有用:

  • 各個節點運行在不同版本的Erlang和RabbitMQ上

  • 網絡環境不穩定,如廣域網當中

Federation的作用:

Shovel插件

Shovel與Federation具備的數據轉發功能類似。Shovel能夠可靠,持續的從一個Broker中的隊列(作爲源端,即source)拉取數據並轉發至另一個Broker的交換器(作爲目的端,即destination)

Shovel的主要優勢:
松耦合,shovel可以移動位於不同管理域中的Broker或者集羣上的消息,這些Broker或者集羣可以包含不同的用戶和vhost,也可以使用不同的RabbitMQ和Erlang版本
支持廣域網,Shovel插件同樣基於AMQP協議在Broker之間進行通信,被設計成可以容忍時斷時續的連通情形,並且能夠保證消息的可靠性
高度定製,當Shovel成功連接後,可以對其進行配置以執行相關的AMQP命令

Federation/Shovel與Cluster的區別與聯繫

本文分享自微信公衆號 - Coding Diary(gh_7e1f05090980)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章