目錄導航
前言
前面的章節我們分析了《Kafka使用以及原理分析》,《ActiveMQ的使用以及原理分析》,從這一節開始,來說說RabbitMQ。
我們知道,消息中間件的特點有:削峯,異步,解耦,區別點在於語言支持、吞吐量、持久化等差異。
關於RabbitMQ,我會圍繞以下幾點進行展開
- 典型應用場景
- 基本介紹
- Java API編程
- 進階知識
- UI管理界面
- Spring配置方式集成RabbitMQ
- Spring Boot集成RabbitMQ
共計分爲兩小節進行闡述:
- 初識RabbitMQ
- RabbitMQ進階
Tips:本文末有福利哦,不要錯過!另本節的所有演示代碼已上傳GitHub,地址我也在文末一併送出~
本人鄭重承諾不玩微信公衆號,不接任何廣告,無任何收費以及廣告傾向,只爲打造CSDN文化淨土,一心知識分享,天道酬勤,讓我們技術人攜手共進,風雨同舟,開創互聯網的美好未來,從今天的你我做起~
典型應用場景
-
跨系統的異步通信 人民銀行二代支付系統,使用重量級消息隊列 IBM MQ,異步,解耦,削峯都有體現。
-
應用內的同步變成異步秒殺:自己發送給自己
-
基於Pub/Sub模型實現的事件驅動 放款失敗通知、提貨通知、購買碎屏保 系統間同步數據 摒棄ELT(比如全量同步商戶數據); 摒棄API(比如定時增量獲取用戶、獲取產品,變成增量廣播)。
-
利用RabbitMQ實現事務的最終一致性
基本介紹
關於RabbitMQ的安裝,文末後記有相關安裝的文章鏈接,筆者親自試用沒有問題,包括Windows與Linux的版本,這裏就不細說了,直接上乾貨~
AMQP協議
我們說爲什麼要有設計模式?其實最重要的在與規範化,大家達成一致纔好幹活,而AMQP就是爲解決各種消息中間件的多語言,多平臺的不統一的一個協議,它制訂了一套消息通信的規範,使得軟件開發變得甜甜的。
AMQP,即Advanced Message Queuing Protocol,一個提供統一消息服務的應用層標準高級消息隊列協議,是應用層協議的一個開放標準,爲面向消息的中間件設計。基於此協議的客戶端與消息中間件可傳遞消息,並不受客戶端/中間件同產品、不同的開發語言等條件的限制。
AMQP的實現有:RabbitMQ、OpenAMQ、Apache Qpid、Redhat Enterprise MRG、AMQP Infrastructure、ØMQ、Zyre等。
RabbitMQ的特性
RabbitMQ使用Erlang語言編寫,使用Mnesia數據庫存儲消息。
-
可靠性(Reliability) RabbitMQ 使用一些機制來保證可靠性,如持久化、傳輸確認、發佈確認。
-
靈活的路由(Flexible Routing) 在消息進入隊列之前,通過 Exchange 來路由消息的。對於典型的路由功能,RabbitMQ 已經提供了一些內置的 Exchange 來實現。針對更復雜的路由功能,可以將多個 Exchange 綁定在一起,也通過插件機制實現自己的 Exchange 。
-
消息集羣(Clustering) 多個 RabbitMQ 服務器可以組成一個集羣,形成一個邏輯 Broker 。
-
高可用(Highly Available Queues) 隊列可以在集羣中的機器上進行鏡像,使得在部分節點出問題的情況下隊列仍然可用。
-
多種協議(Multi-protocol) RabbitMQ 支持多種消息隊列協議,比如 AMQP、STOMP、MQTT 等等。
-
多語言客戶端(Many Clients) RabbitMQ 幾乎支持所有常用語言,比如 Java、.NET、Ruby、PHP、C#、 JavaScript 等等。
-
管理界面(Management UI) RabbitMQ 提供了一個易用的用戶界面,使得用戶可以監控和管理消息、集羣中的節點。
-
插件機制(Plugin System)RabbitMQ提供了許多插件,以實現從多方面擴展,當然也可以編寫自己的插件。
工作模型
這裏解釋一下圖上的幾個概念:
概念 | 解釋 |
---|---|
Broker | 即RabbitMQ的實體服務器。提供一種傳輸服務,維護一條從生產者到消費者的傳輸線路,保證消息數據能按照指定的方式傳輸。 |
Exchange | 消息交換機。指定消息按照什麼規則路由到哪個隊列Queue。 |
Queue | 消息隊列。消息的載體,每條消息都會被投送到一個或多個隊列中。 |
Binding | 綁定。作用就是將Exchange和Queue按照某種路由規則綁定起來。 |
Routing | 路由關鍵字。Exchange根據Routing Key進行消息投遞。定義綁定時指定的關鍵字稱爲Key Binding Key。 |
Vhost | 虛擬主機。一個Broker可以有多個虛擬主機,用作不同用戶的權限分離。一個虛擬主機持有一組Exchange、Queue和Binding。 |
Producer | 消息生產者。主要將消息投遞到對應的Exchange上面。一般是獨立的程序。 |
Consumer | 消息消費者。消息的接收者,一般是獨立的程序。 |
Connection | Producer 和 Consumer 與Broker之間的TCP長連接。 |
Channel | 消息通道,也稱信道。在客戶端的每個連接裏可以建立多個Channel,每個Channel代表一個會話任務。在RabbitMQ Java Client API中,channel上定義了大量的編程接口。 |
三種主要的交換機
Direct Exchange直連交換機
定義:直連類型的交換機與一個隊列綁定時,需要指定一個明確的binding key。
路由規則:發送消息到直連類型的交換機時,只有routing key跟binding key完全匹配時,綁定的隊列才能收到消息。
例如:
//只有隊列1能收到消息
channel.basicPublish("MY_DIRECT_EXCHANGE", "key1", null, msg.getBytes());
Topic Exchange主題交換機
定義:主題類型的交換機與一個隊列綁定時,可以指定按模式匹配的routing key。
通配符有兩個,*代表匹配一個單詞。#代表匹配零個或者多個單詞。單詞與單詞之間用 . 隔開。
路由規則:發送消息到主題類型的交換機時,routing key符合binding key的模式時,綁定的隊列才能收到消息。
例如:
//只有隊列1能收到消息
channel.basicPublish("MY_TOPIC_EXCHANGE", "sh.abc", null, msg.getBytes());
//隊列2和隊列3能收到消息
channel.basicPublish("MY_TOPIC_EXCHANGE", "bj.book", null, msg.getBytes());
//只有隊列4能收到消息
channel.basicPublish("MY_TOPIC_EXCHANGE", "abc.def.food", null, msg.getBytes());
Fanout Exchange廣播交換機
定義:廣播類型的交換機與一個隊列綁定時,不需要指定binding key。
路由規則:當消息發送到廣播類型的交換機時,不需要指定routing key,所有與之綁定的隊列都能收到消息。
例如:
//3個隊列都會收到消息
channel.basicPublish("MY_FANOUT_EXCHANGE", "", null, msg.getBytes());
Java API編程
創建Maven工程,pom.xml引入依賴
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.1.0</version>
</dependency>
生產者
public class MyProducer {
private final static String EXCHANGE_NAME = "SIMPLE_EXCHANGE";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
// 連接IP
factory.setHost("192.168.200.111");
// 連接端口
factory.setPort(5672);
// 虛擬機
factory.setVirtualHost("/");
// 用戶
factory.setUsername("guest");
factory.setPassword("guest");
// 建立連接
Connection conn = factory.newConnection();
// 創建消息通道
Channel channel = conn.createChannel();
// 發送消息
String msg = "Hello world, Rabbit MQ";
// String exchange, String routingKey, BasicProperties props, byte[] body
channel.basicPublish(EXCHANGE_NAME, "best", null, msg.getBytes());
channel.close();
conn.close();
}
}
消費者
public class MyConsumer {
private final static String EXCHANGE_NAME = "SIMPLE_EXCHANGE";
private final static String QUEUE_NAME = "SIMPLE_QUEUE";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
// 連接IP
factory.setHost("192.168.200.111");
// 默認監聽端口
factory.setPort(5672);
// 虛擬機
factory.setVirtualHost("/");
// 設置訪問的用戶
factory.setUsername("guest");
factory.setPassword("guest");
// 建立連接
Connection conn = factory.newConnection();
// 創建消息通道
Channel channel = conn.createChannel();
// 聲明交換機
// String exchange, String type, boolean durable, boolean autoDelete, Map<String, Object> arguments
channel.exchangeDeclare(EXCHANGE_NAME,"direct",false, false, null);
// 聲明隊列
// String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" Waiting for message....");
// 綁定隊列和交換機
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"best");
// 創建消費者
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
String msg = new String(body, "UTF-8");
System.out.println("Received message : '" + msg + "'");
System.out.println("consumerTag : " + consumerTag );
System.out.println("deliveryTag : " + envelope.getDeliveryTag() );
}
};
// 開始獲取消息
// String queue, boolean autoAck, Consumer callback
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
測試用例
slf4j警告可以忽略,不影響測試~
消費者:
生產者:
Web頁面:
參數說明
如上面代碼所示,我們看看配置通信的過程中參數的設置
聲明交換機的參數
// 聲明交換機
// String exchange, String type, boolean durable, boolean autoDelete, Map<String, Object> arguments
channel.exchangeDeclare(EXCHANGE_NAME,"direct",false, false, null);
String type:交換機的類型,direct, topic, fanout中的一種。
boolean durable:是否持久化,代表交換機在服務器重啓後是否還存在。
Channel.queueDeclare方法的構造函數
聲明隊列的參數
// 聲明隊列
// String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
boolean durable:是否持久化,代表隊列在服務器重啓後是否還存在。
boolean exclusive:是否排他性隊列。排他性隊列只能在聲明它的Connection中使用,連接斷開時自動刪除。
boolean autoDelete:是否自動刪除。如果爲true,至少有一個消費者連接到這個隊列,之後所有與這個隊列連接的消費者都斷開時,隊列會自動刪除。
Map<String, Object> arguments:隊列的其他屬性,例如x-message-ttl、x-expires、x-max-length、x-max-length-bytes、x-dead-letter-exchange、x-dead-letter-routing-key、x-max-priority。
消息屬性BasicProperties
消息的全部屬性有14個,以下列舉了一些主要的參數:
參數 | 釋義 |
---|---|
Map<String,Object> headers | 消息的其他自定義參數 |
Integer deliveryMode | 2持久化,其他:瞬態 |
Integer priority | 消息的優先級 |
String correlationId | 關聯ID,方便RPC相應與請求關聯 |
String replyTo | 回調隊列 |
String expiration | TTL,消息過期時間,單位毫秒 |
RabbitMQ的進階知識
- 怎麼自動刪除沒人消費的消息?
- 無法路由的消息,去了哪裏?
- 可以讓消息優先得到消費嗎?
- 如何實現延遲發送消息?
- MQ怎麼實現RPC?
- RabbitMQ流量控制怎麼做?設置隊列大小有用嗎?
TTL
TTL: time to live
- 消息的過期時間
- 隊列的過期時間
消息的過期時間
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setUri(ResourceUtil.getKey("rabbitmq.uri"));
// 建立連接
Connection conn = factory.newConnection();
// 創建消息通道
Channel channel = conn.createChannel();
String msg = "Hello world, Rabbit MQ, DLX MSG";
// 通過隊列屬性設置消息過期時間
Map<String, Object> argss = new HashMap<String, Object>();
argss.put("x-message-ttl",6000);
// 聲明隊列(默認交換機AMQP default,Direct)
// String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
channel.queueDeclare("TEST_TTL_QUEUE", false, false, false, argss);
// 對每條消息設置過期時間
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.deliveryMode(2) // 持久化消息
.contentEncoding("UTF-8")
.expiration("10000") // TTL
.build();
// 此處兩種方式設置消息過期時間的方式都使用了,將以較小的數值爲準
// 發送消息
channel.basicPublish("", "TEST_DLX_QUEUE", properties, msg.getBytes());
channel.close();
conn.close();
}
死信隊列
死信隊列: 生產者生產一條消息後,但此消息沒有路由到任何一個隊列的時候,這個消息就變成了死信,如果我們沒有設置一些比如備份交換機、死信交換機這種策略的話,那麼這樣的消息就會被直接刪除,但是有種方法可以將死信消息堆積起來,就是DLX。
有三種情況消息會進入DLX(Dead Letter Exchange)死信交換機。
- (NACK || Reject ) && requeue == false
// 創建消費者,並接收消息
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
String msg = new String(body, "UTF-8");
System.out.println("Received message : '" + msg + "'");
if (msg.contains("拒收")){
// 拒絕消息
// requeue:是否重新入隊列,true:是;false:直接丟棄,相當於告訴隊列可以直接刪除掉
// TODO 如果只有這一個消費者,requeue 爲true 的時候會造成消息重複消費
channel.basicReject(envelope.getDeliveryTag(), false);
} else if (msg.contains("異常")){
// 批量拒絕
// requeue:是否重新入隊列
// TODO 如果只有這一個消費者,requeue 爲true 的時候會造成消息重複消費
channel.basicNack(envelope.getDeliveryTag(), true, false);
} else {
// 手工應答
// 如果不應答,隊列中的消息會一直存在,重新連接的時候會重複消費
channel.basicAck(envelope.getDeliveryTag(), true);
}
}
};
- 消息過期
前面提到,生產者生產一條消息後,但此消息沒有路由到任何一個隊列的時候,這個消息就變成了死信~
// 指定隊列的死信交換機
Map<String,Object> arguments = new HashMap<String,Object>();
arguments.put("x-dead-letter-exchange","DLX_EXCHANGE");
arguments.put("x-expires","9000"); // 設置隊列的TTL
- 隊列達到最大長度(先入隊的消息會被髮送到DLX)
// 指定隊列的死信交換機
Map<String,Object> arguments = new HashMap<String,Object>();
arguments.put("x-dead-letter-exchange","DLX_EXCHANGE");
arguments.put("x-max-length", 4); // 如果設置了隊列的最大長度,超過長度時,先入隊的消息會被髮送到DLX
那麼怎麼獲取死信的消息呢?
可以設置一個死信隊列(Dead Letter Queue)與DLX綁定,即可以存儲Dead Letter,消費者可以監聽這個隊列取走消息。
// 指定隊列的死信交換機
Map<String,Object> arguments = new HashMap<String,Object>();
arguments.put("x-dead-letter-exchange","DLX_EXCHANGE");
// 聲明隊列(默認交換機AMQP default,Direct)
// String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
channel.queueDeclare("TEST_DLX_QUEUE", false, false, false, arguments);
// 聲明死信交換機
channel.exchangeDeclare("DLX_EXCHANGE","topic", false, false, false, null);
// 聲明死信隊列
channel.queueDeclare("DLX_QUEUE", false, false, false, null);
// 綁定,此處 Dead letter routing key 設置爲 #
channel.queueBind("DLX_QUEUE","DLX_EXCHANGE","#");
System.out.println(" Waiting for message....");
流程如是:
演示代碼參考:rabbitmq-demo/rabbitmq-javaapi/com.test.ack
優先級隊列
設置一個隊列的最大優先級:
Map<String, Object> headers = new HashMap<String, Object>();
headers.put("name", "gupao");
headers.put("level", "top");
// 聲明隊列(默認交換機AMQP default,Direct)
// String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
發送消息時指定消息當前的優先級:
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.deliveryMode(2) // 2代表持久化
.contentEncoding("UTF-8") // 編碼
.expiration("10000") // TTL,過期時間
.headers(headers) // 自定義屬性
.priority(5) // 優先級,默認爲5,配合隊列的 x-max-priority 屬性使用
.messageId(String.valueOf(UUID.randomUUID()))
.build();
// 發送消息
// String exchange, String routingKey, BasicProperties props, byte[] body
channel.basicPublish("", QUEUE_NAME, properties, msg.getBytes());
優先級高的消息可以優先被消費,但是:只有消息堆積(消息的發送速度大於消費者的消費速度)的情況下優先級纔有意義。
演示代碼參考:rabbitmq-demo/rabbitmq-javaapi/com.test.message
延遲隊列
RabbitMQ本身不支持延遲隊列。可以使用TTL結合DLX的方式來實現消息的延遲投遞,即把DLX跟某個隊列綁定,到了指定時間,消息過期後,就會從DLX路由到這個隊列,消費者可以從這個隊列取走消息。
另一種方式是使用rabbitmq-delayed-message-exchange插件。
當然,將需要發送的信息保存在數據庫,使用任務調度系統掃描然後發送也是可以實現的。
演示代碼參考:rabbitmq-demo/rabbitmq-javaapi/com.test.dlx
PRC
RabbitMQ實現RPC的原理:服務端處理消息後,把響應消息發送到一個響應隊列,客戶端再從響應隊列取到結果。
其中的問題:Client收到消息後,怎麼知道應答消息是回覆哪一條消息的?所以必須有一個唯一ID來關聯,就是correlationId。
演示代碼參考:rabbitmq-demo/rabbitmq-javaapi/com.test.rpc
服務端流控(Flow Control)
RabbitMQ 會在啓動時檢測機器的物理內存數值。默認當 MQ 佔用 40% 以上內存時,MQ 會主動拋出一個內存警告並阻塞所有連接(Connections)。可以通過修改 rabbitmq.config 文件來調整內存閾值,默認值是 0.4,如下所示: [{rabbit, [{vm_memory_high_watermark, 0.4}]}].
默認情況,如果剩餘磁盤空間在 1GB 以下,RabbitMQ 主動阻塞所有的生產者。這個閾值也是可調的。
注意隊列長度只在消息堆積的情況下有意義,而且會刪除先入隊的消息,不能實現服務端限流。
消費端限流
在AutoACK爲false的情況下,如果一定數目的消息(通過基於consumer或者channel設置Qos的值)未被確認前,不進行消費新的消息。
//非自動確認消息的前提下,如果一定數目的消息(通過基於consume或者channel設置Qos的值)未被確認前,不進行消費新的消息。
// 因爲Consumer2的處理速率很慢,收到兩條消息後都沒有發送ACK,隊列不會再發送消息給Consumer2
channel.basicQos(2);
channel.basicConsume(QUEUE_NAME, false, consumer);
演示代碼參考:rabbitmq-demo/rabbitmq-javaapi/com.test.limit
UI管理界面的使用
管理插件提供了更簡單的管理方式。
啓用管理插件
- Windows啓用管理插件
cd C:\Program Files\RabbitMQ Server\rabbitmq_server-3.6.6\sbin
rabbitmq-plugins.bat enable rabbitmq_management
- Linux啓用管理插件
cd /data/program/rabbitmq_server-3.7.16/sbin
./rabbitmq-plugins enable rabbitmq_management
管理界面訪問端口
默認端口是15672,默認用戶guest,密碼guest。guest用戶默認只能在本機訪問。
登陸後:
Linux 創建RabbitMQ用戶
例如創建用戶admin,密碼admin,授權訪問所有的Vhost
firewall-cmd --permanent --add-port=15672/tcp
firewall-cmd --reload
rabbitmqctl add_user admin admin
rabbitmqctl set_user_tags admin administrator rabbitmqctl set_permissions -p / admin “." ".” “.*”
Spring配置方式集成RabbitMQ
步驟
1、創建Maven工程,pom.xml引入依賴
<!--rabbitmq依賴 -->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>1.3.5.RELEASE</version>
</dependency>
2、src/main/resouces目錄,創建rabbitMQ.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit-1.2.xsd">
<!--配置connection-factory,指定連接rabbit server參數 -->
<rabbit:connection-factory id="connectionFactory" virtual-host="/" username="guest" password="guest" host="192.168.200.111" port="5672" />
<!--通過指定下面的admin信息,當前producer中的exchange和queue會在rabbitmq服務器上自動生成 -->
<rabbit:admin id="connectAdmin" connection-factory="connectionFactory" />
<!--######分隔線######-->
<!--定義queue -->
<rabbit:queue name="MY_FIRST_QUEUE" durable="true" auto-delete="false" exclusive="false" declared-by="connectAdmin" />
<!--定義direct exchange,綁定MY_FIRST_QUEUE -->
<rabbit:direct-exchange name="MY_DIRECT_EXCHANGE" durable="true" auto-delete="false" declared-by="connectAdmin">
<rabbit:bindings>
<rabbit:binding queue="MY_FIRST_QUEUE" key="FirstKey">
</rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
<!--定義rabbit template用於數據的接收和發送 -->
<rabbit:template id="amqpTemplate" connection-factory="connectionFactory" exchange="MY_DIRECT_EXCHANGE" />
<!--消息接收者 -->
<bean id="messageReceiver" class="com.test.consumer.FirstConsumer"></bean>
<!--queue listener 觀察 監聽模式 當有消息到達時會通知監聽在對應的隊列上的監聽對象 -->
<rabbit:listener-container connection-factory="connectionFactory">
<rabbit:listener queues="MY_FIRST_QUEUE" ref="messageReceiver" />
</rabbit:listener-container>
<!--定義queue -->
<rabbit:queue name="MY_SECOND_QUEUE" durable="true" auto-delete="false" exclusive="false" declared-by="connectAdmin" />
<!-- 將已經定義的Exchange綁定到MY_SECOND_QUEUE,注意關鍵詞是key -->
<rabbit:direct-exchange name="MY_DIRECT_EXCHANGE" durable="true" auto-delete="false" declared-by="connectAdmin">
<rabbit:bindings>
<rabbit:binding queue="MY_SECOND_QUEUE" key="SecondKey"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
<!-- 消息接收者 -->
<bean id="receiverSecond" class="com.test.consumer.SecondConsumer"></bean>
<!-- queue litener 觀察 監聽模式 當有消息到達時會通知監聽在對應的隊列上的監聽對象 -->
<rabbit:listener-container connection-factory="connectionFactory">
<rabbit:listener queues="MY_SECOND_QUEUE" ref="receiverSecond" />
</rabbit:listener-container>
<!--######分隔線######-->
<!--定義queue -->
<rabbit:queue name="MY_THIRD_QUEUE" durable="true" auto-delete="false" exclusive="false" declared-by="connectAdmin" />
<!-- 定義topic exchange,綁定MY_THIRD_QUEUE,注意關鍵詞是pattern -->
<rabbit:topic-exchange name="MY_TOPIC_EXCHANGE" durable="true" auto-delete="false" declared-by="connectAdmin">
<rabbit:bindings>
<rabbit:binding queue="MY_THIRD_QUEUE" pattern="#.Third.#"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
<!--定義rabbit template用於數據的接收和發送 -->
<rabbit:template id="amqpTemplate2" connection-factory="connectionFactory" exchange="MY_TOPIC_EXCHANGE" />
<!-- 消息接收者 -->
<bean id="receiverThird" class="com.test.consumer.ThirdConsumer"></bean>
<!-- queue litener 觀察 監聽模式 當有消息到達時會通知監聽在對應的隊列上的監聽對象 -->
<rabbit:listener-container connection-factory="connectionFactory">
<rabbit:listener queues="MY_THIRD_QUEUE" ref="receiverThird" />
</rabbit:listener-container>
<!--######分隔線######-->
<!--定義queue -->
<rabbit:queue name="MY_FOURTH_QUEUE" durable="true" auto-delete="false" exclusive="false" declared-by="connectAdmin" />
<!-- 定義fanout exchange,綁定MY_FIRST_QUEUE 和 MY_FOURTH_QUEUE -->
<rabbit:fanout-exchange name="MY_FANOUT_EXCHANGE" auto-delete="false" durable="true" declared-by="connectAdmin" >
<rabbit:bindings>
<rabbit:binding queue="MY_FIRST_QUEUE"></rabbit:binding>
<rabbit:binding queue="MY_FOURTH_QUEUE"></rabbit:binding>
</rabbit:bindings>
</rabbit:fanout-exchange>
<!-- 消息接收者 -->
<bean id="receiverFourth" class="com.test.consumer.FourthConsumer"></bean>
<!-- queue litener 觀察 監聽模式 當有消息到達時會通知監聽在對應的隊列上的監聽對象 -->
<rabbit:listener-container connection-factory="connectionFactory">
<rabbit:listener queues="MY_FOURTH_QUEUE" ref="receiverFourth" />
</rabbit:listener-container>
</beans>
整體流程如下:
3、配置applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<import resource="classpath*:rabbitMQ.xml" />
<!-- 掃描指定package下所有帶有如 @Controller,@Service,@Resource 並把所註釋的註冊爲Spring Beans -->
<context:component-scan base-package="com.test.*" />
<!-- 激活annotation功能 -->
<context:annotation-config />
<!-- 激活annotation功能 -->
<context:spring-configured />
</beans>
4、src/main/resouces目錄,log4j.properties
5、編寫生產者
@Service
public class MessageProducer {
private Logger logger = LoggerFactory.getLogger(MessageProducer.class);
@Autowired
@Qualifier("amqpTemplate")
private AmqpTemplate amqpTemplate;
@Autowired
@Qualifier("amqpTemplate2")
private AmqpTemplate amqpTemplate2;
/**
* 演示三種交換機的使用
*
* @param message
*/
public void sendMessage(Object message) {
logger.info("Send message:" + message);
// amqpTemplate 默認交換機 MY_DIRECT_EXCHANGE
// amqpTemplate2 默認交換機 MY_TOPIC_EXCHANGE
// Exchange 爲 direct 模式,直接指定routingKey
amqpTemplate.convertAndSend("FirstKey", "[Direct,FirstKey] "+message);
amqpTemplate.convertAndSend("SecondKey", "[Direct,SecondKey] "+message);
// Exchange模式爲topic,通過topic匹配關心該主題的隊列
amqpTemplate2.convertAndSend("msg.Third.send","[Topic,msg.Third.send] "+message);
// 廣播消息,與Exchange綁定的所有隊列都會收到消息,routingKey爲空
amqpTemplate2.convertAndSend("MY_FANOUT_EXCHANGE",null,"[Fanout] "+message);
}
}
6、編寫4個消費者
public class FirstConsumer implements MessageListener {
private Logger logger = LoggerFactory.getLogger(FirstConsumer.class);
public void onMessage(Message message) {
logger.info("The first consumer received message : " + message.getBody());
}
}
類比再寫三個消費者,由於代碼邏輯相同,這裏不貼出來了,有興趣的朋友可以看我的完整代碼:
演示代碼參考:rabbitmq-demo/spring-rabbitmq
代碼測試:
運行測試用例,每個一秒發送一次消息,生產者指定三種交換機,四個消費者根據指定的消費者這100條數據。
日誌打印也證實了實驗的猜想。
Spring Boot集成RabbitMQ
步驟
springBoot封裝了mq的操作,所以springboot集成mq不需要導入依賴,直接使用AmqpTemplate即可。
首先配置交換機與隊列的關係:
@Configuration
public class RabbitConfig {
// 兩個交換機
@Bean("topicExchange")
public TopicExchange getTopicExchange(){
return new TopicExchange("TOPIC_EXCHANGE");
}
@Bean("fanoutExchange")
public FanoutExchange getFanoutExchange(){
return new FanoutExchange("FANOUT_EXCHANGE");
}
// 三個隊列
@Bean("firstQueue")
public Queue getFirstQueue(){
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-message-ttl",6000);
Queue queue = new Queue("FIRST_QUEUE", false, false, true, args);
return queue;
}
@Bean("secondQueue")
public Queue getSecondQueue(){
return new Queue("SECOND_QUEUE");
}
@Bean("thirdQueue")
public Queue getThirdQueue(){
return new Queue("THIRD_QUEUE");
}
// 兩個綁定
@Bean
public Binding bindSecond(@Qualifier("secondQueue") Queue queue,@Qualifier("topicExchange") TopicExchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("#.test.#");
}
@Bean
public Binding bindThird(@Qualifier("thirdQueue") Queue queue,@Qualifier("fanoutExchange") FanoutExchange exchange){
return BindingBuilder.bind(queue).to(exchange);
}
}
寫三個生產者類,代碼類似,只貼出一個生產者,完整代碼詳見本文末GItlab地址:
@Component
@RabbitListener(queues = "FIRST_QUEUE")
public class FirstConsumer {
@RabbitHandler
public void process(String msg){
System.out.println(" first queue received msg : " + msg);
}
}
消費者:
@Component
public class MyProvider {
@Autowired
AmqpTemplate amqpTemplate;
public void send(){
// 發送4條消息
amqpTemplate.convertAndSend("","FIRST_QUEUE","-------- a direct msg");
amqpTemplate.convertAndSend("TOPIC_EXCHANGE","shanghai.test.teacher","-------- a topic msg : shanghai.test.teacher");
amqpTemplate.convertAndSend("TOPIC_EXCHANGE","changsha.test.student","-------- a topic msg : changsha.test.student");
amqpTemplate.convertAndSend("FANOUT_EXCHANGE","","-------- a fanout msg");
}
}
測試用例:
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootMq0904ApplicationTests {
@Autowired
MyProvider provider;
@Test
public void contextLoads() {
provider.send();
}
}
這樣,就簡單的通過springboot集成rabbitmq~
演示代碼參考:rabbitmq-demo/springboot-mq
後記
-
文章推薦:
RabbitMQ官網文章中文翻譯系列
Linux-RPM包安裝RabbitMQ
Linux-xz包安裝RabbitMQ
Windows下安裝RabbitMQ -
本節的代碼demo
test for rabbitmq-api:https://github.com/harrypottry/rabbitmq-demo
spring 集成rabbitmq:https://github.com/harrypottry/springboot-rabbit -
福利來啦~
《RabbitMQ高效部署分佈式消息隊列》提取碼:m8nv -
更多架構知識,歡迎關注本套Java系列文章,地址導航:Java架構師成長之路