RabbitMQ
-
RabbitMQ主要基于AMQP協議實現
AMQP (Advanced Message Queuing Protocol) 高級消息隊列協議,是應用層協議的一個開放標準,為面向消息的中間件設計
-
Producer
生產者,投遞消息的一方。用于創建消息,然后發布到RabbitMQ中
消息一般分為兩個部分:消息體 、附加信息- 消息體一般是一個帶有業務邏輯結構的數據,比如JSON字符串
- 附加信息用來表述這條消息,如交換器名稱、路由鍵和一些自定義屬性等等
-
Broker
消息中間件的服務節點;單臺機器部署Broker就相當于是整個MQ服務器
-
Virtual Host
虛擬主機,表示一批交換器、消息隊列和相關對象;虛擬主機是共享相同身份認證和加密環境的獨立服務器域
-
Channel
頻道或信道,是建立在Connection連接之上的一種輕量級的連接;一個Connection可以創建任意數量的Channel
大部分操作都是在Channel這個接口中完成的,包括定義隊列的聲明queueDeclare、交換機的聲明exchangeDeclare、隊列的綁定queueBind、發布消息basicPublish、消費消息basicConsume等
-
RoutingKey
路由鍵;生產者將消息發給交換器的時候,一般會指定一個RoutingKey,用來指定這個消息的路由規則;RoutingKey需要與交換器類型和綁定鍵(BindingKey)聯合使用
-
Exchange
交換器,生產者將消費發送到Exchange,再由它將消息路由到一個或多個隊列中,如果路由不到,或返回或直接丟棄
- fanout:扇形交換機,會把所有消息路由到與之綁定的所有隊列中
- direct:直連交換機,會根據BindingKey與RoutingKey匹配發送消息
- topic:主題交換機,與direct類似,但是可以通過通配符模糊匹配
- headers:頭交換機,根據消息頭部中帶的值進行匹配
-
Queue
隊列,是RabbitMQ的內部對象,用于存儲消息
-
Binding
綁定,RabbitMQ中通過綁定將交換器與隊列關聯起來,在綁定的時候一般會指定一個綁定鍵(BindingKey),這樣交換器就知道如何正確的將消息路由到哪個隊列中
-
Consumer
消費者,接受消息的一方;消費者連接到RabbitMQ服務器并定于到隊列上
RabbitMQ運轉流程
-
生產者發送消息的過程:
1. 生產者連接到RabbitMQ Broker,建立一個連接(Connection),開啟一個信道(Channel)
2. 生產者聲明一個交換器,并設置相關屬性,比如交換機類型、是否持久化等
3. 生產者聲明一個隊列并設置相關屬性,比如是否排他,是否持久化,是否自動刪除等
4. 生產者通過路由鍵將交換器和隊列綁定起來
5. 生產者發送消息至RabbitMQ Broker,其中包含路由鍵、交換器等信息
6. 相應的交換器根據接受到的路由鍵排查相匹配的隊列
7. 如果找到,則將從生產者發送過來的消息存入相應的隊列中
8. 如果沒找到,則根據生產者配置的屬性選擇丟棄還是回退給生產者
9. 關閉信道,關閉連接
-
消費者接收消息的過程:
1. 消費者連接到RabbitMQ Broker,建立一個連接(Connection),開啟一個信道(Channel)
2. 消費者向RabbitMQ Broker 請求消費相應隊列中的消息,可能會設置相應的回調函數,以及做一些準備工作
3. 等待RabbitMQ Broker 回應并投遞相應隊列中的消息,接收消息
4. 消費者確認(ack)接收到的消息
5. RabbitMQ 從隊列中刪除相應已被確認的消息
6. 關閉信道、關閉連接
RabbitMQ 安裝和使用
一、安裝依賴環境
在 http://www.rabbitmq.com/which-erlang.html 頁面查看安裝rabbitmq需要安裝erlang對應的版本
在 https://github.com/rabbitmq/erlang-rpm/releases 頁面找到需要下載的erlang版本,
erlang-*.centos.x86_64.rpm
就是centos版本的。-
復制下載地址后,使用wget命令下載
wget -P /home/download https://github.com/rabbitmq/erlang-rpm/releases/download/v21.2.3/erlang-21.2.3-1.el7.centos.x86_64.rpm
-
安裝 Erlang
sudo rpm -Uvh /home/download/erlang-21.2.3-1.el7.centos.x86_64.rpm
-
安裝 socat
sudo yum install -y socat
二、安裝RabbitMQ
-
在官方下載頁面找到CentOS7版本的下載鏈接,下載rpm安裝包
wget -P /home/download https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.7.9/rabbitmq-server-3.7.9-1.el7.noarch.rpm
提示:可以在
https://github.com/rabbitmq/rabbitmq-server/tags下載歷史版本
-
安裝RabbitMQ
sudo rpm -Uvh /home/download/rabbitmq-server-3.7.9-1.el7.noarch.rpm
三、啟動和關閉
-
啟動服務
sudo systemctl start rabbitmq-server
-
查看狀態
sudo systemctl status rabbitmq-server
-
停止服務
sudo systemctl stop rabbitmq-server
-
設置開機啟動
sudo systemctl enable rabbitmq-server
四、開啟Web管理插件
-
開啟插件
rabbitmq-plugins enable rabbitmq_management
說明:rabbitmq有一個默認的guest用戶,但只能通過localhost訪問,所以需要添加一個能夠遠程訪問的用戶。
-
添加用戶
rabbitmqctl add_user admin admin
-
為用戶分配操作權限
rabbitmqctl set_user_tags admin administrator
-
為用戶分配資源權限
rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"
五、防火墻添加端口
- RabbitMQ 服務啟動后,還不能進行外部通信,需要將端口添加都防火墻
-
添加端口
sudo firewall-cmd --zone=public --add-port=4369/tcp --permanent sudo firewall-cmd --zone=public --add-port=5672/tcp --permanent sudo firewall-cmd --zone=public --add-port=25672/tcp --permanent sudo firewall-cmd --zone=public --add-port=15672/tcp --permanent
-
重啟防火墻
sudo firewall-cmd --reload
多機多節點集群部署
一、 環境準備
-
準備三臺安裝好RabbitMQ 的機器,安裝方法見 安裝步驟
- 10.10.1.41
- 10.10.1.42
- 10.10.1.43
提示:如果使用虛擬機,可以在一臺VM上安裝好RabbitMQ后,創建快照,從快照創建鏈接克隆,會節省很多磁盤空間
二、修改配置文件
-
修改
10.10.1.41
機器上的/etc/hosts
文件sudo vim /etc/hosts
-
添加IP和節點名
10.10.1.41 node1 10.10.1.42 node2 10.10.1.43 node3
修改對應主機的hostname
hostname node1
hostname node2
hostname node3
- 將
10.10.1.41
上的hosts文件復制到另外兩臺機器上sudo scp /etc/hosts root@node2:/etc/ sudo scp /etc/hosts root@node3:/etc/
說明:命令中的root是目標機器的用戶名,命令執行后,可能會提示需要輸入密碼,輸入對應用戶的密碼就行了
- 將
10.10.1.41
上的/var/lib/rabbitmq/.erlang.cookie
文件復制到另外兩臺機器上scp /var/lib/rabbitmq/.erlang.cookie root@node2:/var/lib/rabbitmq/ scp /var/lib/rabbitmq/.erlang.cookie root@node3:/var/lib/rabbitmq/
提示:如果是通過克隆的VM,可以省略這一步
三、防火墻添加端口
- 給每臺機器的防火墻添加端口
-
添加端口
sudo firewall-cmd --zone=public --add-port=4369/tcp --permanent sudo firewall-cmd --zone=public --add-port=5672/tcp --permanent sudo firewall-cmd --zone=public --add-port=25672/tcp --permanent sudo firewall-cmd --zone=public --add-port=15672/tcp --permanent
-
重啟防火墻
sudo firewall-cmd --reload
四、啟動RabbitMQ
-
啟動每臺機器的RabbitMQ
sudo systemctl start rabbitmq-server
或者
rabbitmq-server -detached
-
將
10.10.1.42
加入到集群# 停止RabbitMQ 應用 rabbitmqctl stop_app # 重置RabbitMQ 設置 rabbitmqctl reset # 加入到集群 rabbitmqctl join_cluster rabbit@node1 --ram # 啟動RabbitMQ 應用 rabbitmqctl start_app
-
查看集群狀態,看到
running_nodes,[rabbit@node1,rabbit@node2]
表示節點啟動成功rabbitmqctl cluster_status
提示:在管理界面可以更直觀的看到集群信息
-
將
10.10.1.43
加入到集群# 停止 RabbitMQ 應用 rabbitmqctl stop_app # 重置 RabbitMQ 設置 rabbitmqctl reset # 節點加入到集群 rabbitmqctl join_cluster rabbit@node1 --ram # 啟動 RabbitMQ 應用 rabbitmqctl start_app
重復地3步,查看集群狀態
單機多節點部署
一、環境準備
- 準備一臺已經安裝好RabbitMQ 的機器,安裝方法見 安裝步驟
- 10.10.1.41
二、啟動RabbitMQ
-
在啟動前,先修改RabbitMQ 的默認節點名(非必要),在
/etc/rabbitmq/rabbitmq-env.conf
增加以下內容# RabbitMQ 默認節點名,默認是rabbit NODENAME=rabbit1
-
RabbitMQ 默認是使用服務的啟動的,單機多節點時需要改為手動啟動,先停止運行中的RabbitMQ 服務
sudo systemctl stop rabbitmq-server
-
啟動第一個節點
rabbitmq-server -detached
-
啟動第二個節點
RABBITMQ_NODE_PORT=5673 RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15673}]" RABBITMQ_NODENAME=rabbit2 rabbitmq-server -detached
-
啟動第三個節點
RABBITMQ_NODE_PORT=5674 RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15674}]" RABBITMQ_NODENAME=rabbit3 rabbitmq-server -detached
-
將rabbit2加入到集群
# 停止 rabbit2 的應用 rabbitmqctl -n rabbit2 stop_app # 重置 rabbit2 的設置 rabbitmqctl -n rabbit2 reset # rabbit2 節點加入到 rabbit1的集群中 rabbitmqctl -n rabbit2 join_cluster rabbit1 --ram # 啟動 rabbit2 節點 rabbitmqctl -n rabbit2 start_app
-
將rabbit3加入到集群
# 停止 rabbit3 的應用 rabbitmqctl -n rabbit3 stop_app # 重置 rabbit3 的設置 rabbitmqctl -n rabbit3 reset # rabbit3 節點加入到 rabbit1的集群中 rabbitmqctl -n rabbit3 join_cluster rabbit1 --ram # 啟動 rabbit3 節點 rabbitmqctl -n rabbit3 start_app
-
查看集群狀態,看到
{running_nodes,[rabbit3@node1,rabbit2@node1,rabbit1@node1]}
說明節點已啟動成功。rabbitmqctl cluster_status
提示:在管理界面可以更直觀的看到集群信息
三、防火墻添加端口
- 需要將每個節點的端口都添加到防火墻
-
添加端口
sudo firewall-cmd --zone=public --add-port=4369/tcp --permanent sudo firewall-cmd --zone=public --add-port=5672/tcp --permanent sudo firewall-cmd --zone=public --add-port=25672/tcp --permanent sudo firewall-cmd --zone=public --add-port=15672/tcp --permanent sudo firewall-cmd --zone=public --add-port=5673/tcp --permanent sudo firewall-cmd --zone=public --add-port=25673/tcp --permanent sudo firewall-cmd --zone=public --add-port=15673/tcp --permanent sudo firewall-cmd --zone=public --add-port=5674/tcp --permanent sudo firewall-cmd --zone=public --add-port=25674/tcp --permanent sudo firewall-cmd --zone=public --add-port=15674/tcp --permanent
-
重啟防火墻
sudo firewall-cmd --reload
鏡像隊列模式集群
鏡像隊列屬于RabbitMQ 的高可用方案,見:https://www.rabbitmq.com/ha.html#mirroring-arguments
通過前面的步驟搭建的集群屬于普通模式集群,是通過共享元數據實現集群
-
開啟鏡像隊列模式需要在管理頁面添加策略,添加方式:
進入管理頁面 -> Admin -> Policies(在頁面右側)-> Add / update a policy
-
在表單中填入:
name: ha-all Pattern: ^ Apply to: Queues Priority: 0 Definition: ha-mode = all
參數說明
name: 策略名稱,如果使用已有的名稱,保存后將會修改原來的信息
Apply to:策略應用到什么對象上
Pattern:策略應用到對象時,對象名稱的匹配規則(正則表達式)
Priority:優先級,數值越大,優先級越高,相同優先級取最后一個
Definition:策略定義的類容,對于鏡像隊列的配置來說,只需要包含3個部分:
ha-mode
、ha-params
和ha-sync-mode
。其中,ha-sync-mode
是同步的方式,自動還是手動,默認是自動。ha-mode
和ha-params
組合使用。組合方式如下:
ha-mode ha-params 說明 all (empty) 隊列鏡像到集群類所有節點 exactly count 隊列鏡像到集群內指定數量的節點。如果集群內節點數少于此值,隊列將會鏡像到所有節點。如果大于此值,而且一個包含鏡像的節點停止,則新的鏡像不會在其它節點創建。 nodes nodename 隊列鏡像到指定節點,指定的節點不在集群中不會報錯。當隊列申明時,如果指定的節點不在線,則隊列會被創建在客戶端所連接的節點上。 鏡像隊列模式相比較普通模式,鏡像模式會占用更多的帶寬來進行同步,所以鏡像隊列的吞吐量會低于普通模式
但普通模式不能實現高可用,某個節點掛了后,這個節點上的消息將無法被消費,需要等待節點啟動后才能被消費。
簡單代碼示例
JAVA依賴
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.5.1</version>
</dependency>
Spring中依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
生產者
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 簡單隊列生產者
* 使用RabbitMQ的默認交換器發送消息
*/
public class Producer {
public static void main(String[] args) {
// 1、創建連接工廠
ConnectionFactory factory = new ConnectionFactory();
// 2、設置連接屬性
factory.setHost("192.168.100.242");
factory.setPort(5672);
factory.setUsername("admin");
factory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3、從連接工廠獲取連接
connection = factory.newConnection("生產者");
// 4、從鏈接中創建通道
channel = connection.createChannel();
/**
* 5、聲明(創建)隊列
* 如果隊列不存在,才會創建
* RabbitMQ 不允許聲明兩個隊列名相同,屬性不同的隊列,否則會報錯
*
* queueDeclare參數說明:
* @param queue 隊列名稱
* @param durable 隊列是否持久化
* @param exclusive 是否排他,即是否為私有的,如果為true,會對當前隊列加鎖,其它通道不能訪問,并且在連接關閉時會自動刪除,不受持久化和自動刪除的屬性控制
* @param autoDelete 是否自動刪除,當最后一個消費者斷開連接之后是否自動刪除
* @param arguments 隊列參數,設置隊列的有效期、消息最大長度、隊列中所有消息的生命周期等等
*/
channel.queueDeclare("queue1", false, false, false, null);
// 消息內容
String message = "Hello World!";
// 6、發送消息
channel.basicPublish("", "queue1", null, message.getBytes());
System.out.println("消息已發送!");
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} finally {
// 7、關閉通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
// 8、關閉連接
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
消費者
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 簡單隊列消費者
*/
public class Consumer {
public static void main(String[] args) {
// 1、創建連接工廠
ConnectionFactory factory = new ConnectionFactory();
// 2、設置連接屬性
factory.setHost("192.168.100.242");
factory.setUsername("admin");
factory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3、從連接工廠獲取連接
connection = factory.newConnection("消費者");
// 4、從鏈接中創建通道
channel = connection.createChannel();
/**
* 5、聲明(創建)隊列
* 如果隊列不存在,才會創建
* RabbitMQ 不允許聲明兩個隊列名相同,屬性不同的隊列,否則會報錯
*
* queueDeclare參數說明:
* @param queue 隊列名稱
* @param durable 隊列是否持久化
* @param exclusive 是否排他,即是否為私有的,如果為true,會對當前隊列加鎖,其它通道不能訪問,
* 并且在連接關閉時會自動刪除,不受持久化和自動刪除的屬性控制。
* 一般在隊列和交換器綁定時使用
* @param autoDelete 是否自動刪除,當最后一個消費者斷開連接之后是否自動刪除
* @param arguments 隊列參數,設置隊列的有效期、消息最大長度、隊列中所有消息的生命周期等等
*/
channel.queueDeclare("queue1", false, false, false, null);
// 6、定義收到消息后的回調
DeliverCallback callback = new DeliverCallback() {
public void handle(String consumerTag, Delivery message) throws IOException {
System.out.println("收到消息:" + new String(message.getBody(), "UTF-8"));
}
};
// 7、監聽隊列
channel.basicConsume("queue1", true, callback, new CancelCallback() {
public void handle(String consumerTag) throws IOException {
}
});
System.out.println("開始接收消息");
System.in.read();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} finally {
// 8、關閉通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
// 9、關閉連接
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Spring中使用RabbitMQ
創建隊列
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfiguration {
@Bean
public Queue helloSpring() {
return new Queue("spring.cluster");
}
}
生產者
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@EnableAutoConfiguration
@Import(AppConfiguration.class)
public class ProducerApp {
private static final Logger logger = LoggerFactory.getLogger(ProducerApp.class);
@Autowired
private RabbitTemplate template;
@Autowired
private Queue helloSpring;
@Bean
CommandLineRunner runner() {
return args -> {
template.convertAndSend(helloSpring.getName(), "Hello Spring");
logger.info("消息已發送");
};
}
public static void main(String[] args) {
SpringApplication.run(ProducerApp.class, args);
}
}
消費者
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.messaging.handler.annotation.Payload;
@Configuration
@EnableAutoConfiguration
@RabbitListener(queues = "spring.cluster")
@Import(AppConfiguration.class)
public class ConsumerApp {
private static final Logger logger = LoggerFactory.getLogger(ConsumerApp.class);
@RabbitHandler
public void receive(@Payload String msg) {
logger.info("收到消息:" + msg);
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApp.class, args);
}
}