梗概
RabbitMQ作爲一款高性能的消息隊列中間件,主要的作用有業務解耦,削峯,限流等作用。本文主要圍繞SpringBoot整合RabbitMQ後的基礎使用,介紹一下RabbitMQ中交換機,消息確認,持久化等機制
引入RabbitMQ
第一步,就是需要在pom.xml中引入相關的依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
因爲RabbitMQ是基於amqp協議實現的,amqp其實就是一種消息隊列的協議,後續再寫個關於amqp的博客來聊一下
在application.properties(或者是yml也可以)寫入:
# 你的宿主host ip
spring.rabbitmq.host=192.168.199.136
# 端口號 默認是5672
spring.rabbitmq.port=5672
# 均爲默認值
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
基礎的配置就完成啦~
RabbitMQ總體架構
看到一個圖說的特別好,於是偷過來了(逃)
- Producer和Consumer:這兩個好理解,就是消息的發送方和接收方唄
- Queue:隊列。用來存放生產者推送的消息,而消費者從隊列中取出消息進行消費,一個隊列會與一個或者若干個路由鍵綁定,然後註冊到交換機上,生產者和消費者通過路由鍵來找到對應的隊列
- Exchange:交換機。其實就是起到路由器的作用,一個交換機上可以綁定多個隊列,按照不同的路由鍵來區分
特性
交換機
交換機起到一個路由的作用,一個交換機可以通過路由鍵註冊多個消息隊列,那在RabbitMQ中,交換機的種類的一共有5種,用以支持不同的消息發送方式
Direct
直連模式,這種模式很簡單,就是把路由鍵給寫死,一個隊列與一個路由鍵綁定。Java代碼如下:
配置:
@Configuration
public class DirectRabbitmqConfig {
@Bean
public Queue directQueue(){
// durable:是否持久化,默認是false,持久化隊列:會被存儲在磁盤上,當消息代理重啓時仍然存在,暫存隊列:當前連接有效
// exclusive:默認也是false,只能被當前創建的連接使用,而且當連接關閉後隊列即被刪除。此參考優先級高於durable
// autoDelete:是否自動刪除,當沒有生產者或者消費者使用此隊列,該隊列會自動刪除。
// return new Queue("Testdirect.Queue",true,true,false);
//一般設置一下隊列的持久化就好,其餘兩個就是默認false
return new Queue("direct.Queue",true);
}
//直連交換機
@Bean
public DirectExchange testDirectExchange(){
return new DirectExchange("direct.Exchange",true,false);
}
@Bean
public Binding bindDirect(){
return BindingBuilder.bind(directQueue()).to(testDirectExchange()).with("direct.Rout");
}
}
消費者:
@RabbitListener(queues = "direct.Queue")
@Slf4j
@Component
public class DirectRabbitmqReceiver {
@RabbitHandler
public void onMessage(String str,Channel channel,Message message) throws IOException {
System.out.println(str);
}
}
Controller:
@GetMapping("/mq")
public String sendMsg(){
rabbitTemplate.convertAndSend("direct.Exchange","direct.Rout","Hello World!");
return "SUCCESS";
}
啓動項目,訪問接口,就會發現消息都被消費者一個個消費了。這就是直連
System
System Exchange。其實和Direct沒有啥區別,只不過不需要註冊路由鍵
Topic
Topic用的其實很多,它也需要綁定路由鍵,但最大的特點就是支持路由鍵種加入佔位符:
- *表示一個佔位
- #表示一個或多個佔位
直接看代碼也很好理解:
@Bean
public Queue directQueue(){
// durable:是否持久化,默認是false,持久化隊列:會被存儲在磁盤上,當消息代理重啓時仍然存在,暫存隊列:當前連接有效
// exclusive:默認也是false,只能被當前創建的連接使用,而且當連接關閉後隊列即被刪除。此參考優先級高於durable
// autoDelete:是否自動刪除,當沒有生產者或者消費者使用此隊列,該隊列會自動刪除。
// return new Queue("Testdirect.Queue",true,true,false);
//一般設置一下隊列的持久化就好,其餘兩個就是默認false
return new Queue("direct.Queue",true);
}
@Bean
public Queue directQueue2(){
return new Queue("direct.Queue2",true);
}
@Bean
public TopicExchange testTopicExchange(){
return new TopicExchange("testTopicExchange",true,false);
}
@Bean
public Binding bindTopic(){
return BindingBuilder.bind(directQueue()).to(testTopicExchange()).with("direct.Queue");
}
@Bean
public Binding bindTopic2(){
return BindingBuilder.bind(directQueue2()).to(testTopicExchange()).with("direct.#");
}
消費者就只有兩個,分別對應這兩個隊列就行了
Controller:
@GetMapping("/queue")
public String sendTopic(){
rabbitTemplate.convertAndSend("testTopicExchange","direct.Queue","Queue");
return "SUCCESS";
}
@GetMapping("/queue2")
public String sendTopic2(){
rabbitTemplate.convertAndSend("testTopicExchange","direct.Queue2","Queue2");
return "SUCCESS";
}
調用後發現,如果調用”/queue“的話,兩個消費者都收到了消息;如果調用"/queue2"的話,只有一個消費者收到了消息。
這也很好理解,direct.Queue這個路由鍵,即匹配了direct.Queue,也匹配了direct.#。所以testTopicExchange這個交換機會將其往兩個隊列上推。而direct.Queue2只匹配direct.#
Fanout
Fanout是一個多播的交換機,這個交換機上的路由鍵將失效,只要綁定在該交換機上的隊列,都會收到推到該交換機上的消息
定義一個Fanout交換機:
@Bean
public FanoutExchange testFanoutExchange(){
return new FanoutExchange("",true,false);
}
其他代碼和上面的都差不多
Header:
Header交換機用得很少,它不走路由鍵,只按照Header的字段進行匹配,分爲any和all兩種,any就是隻要有其中一個字段匹配即可,all就是必須全部匹配
持久化
消息隊列在很多場景下都是需要有持久下機制了。否則如果隊列中擠壓了大量消息,此時mq服務器重啓,那麼這些消息就都消失了,因爲這些消息都只存在於內存,而RabbitMQ爲了解決這個問腿,也提供了持久化機制
持久化分爲三種
- 交換機持久化
- 隊列持久化
- 消息持久化
交換機持久化
交換機持久化描述的是當這個交換機上沒有註冊隊列時,這個交換機是否刪除。如果要打開持久化的話也很簡單
@Bean
public DirectExchange testDirectExchange(){
//第二個參數就是是否持久化,第三個參數就是是否自動刪除
return new DirectExchange("direct.Exchange",true,false);
}
消費者類上的註解:
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "direct.Queue",autoDelete = "true"),
exchange = @Exchange(value = "direct.Exchange", type = ExchangeTypes.DIRECT,durable = "true"),
key = "direct.Rout"
)
)
@Exchange註解的durable屬性設置爲true(默認也是true,不設置也可以)。這樣,即使這個交換機沒有隊列,也不會被刪除
隊列持久化
隊列持久化描述的是當這個隊列沒有消費者在監聽時,是否進行刪除。持久化做法:
@Bean
public Queue txQueue(){
//第二個參數就是durable,是否持久化
return new Queue("txQueue",true);
}
消費者類的註解:
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "direct.Queue",autoDelete = "false",durable = "true"),
exchange = @Exchange(value = "direct.Exchange", type = ExchangeTypes.DIRECT,durable = "true"),
key = "direct.Rout"
)
)
可以看到,@Queue註解上,加上了durable="true"的註解。這樣隊列在重啓的時候就不會被刪除了
消息持久化
消息持久化和前面兩個稍微有點不同。消息持久化實際上就是基於確認機制去做的。默認情況下,只要消費者接收到這個消息,這個消息就從隊列上被刪除了。、
但考慮這樣一種場景,接口層接受到一個請求,然後推送一個消息,異步地去更新數據庫。此時對於消費者端來說,一拿到消息,消息就從隊列上被刪除,然後開始執行數據庫更新,但此時數據庫更新失敗了,方法直接返回。但隊列上已經沒有這條消息了,這個更新操作不就沒有完成了嗎?這肯定是有問題的。
所以RabbitMQ就有了消費者確認機制,只有消費者手動確認,消息纔會被刪除,否則該消息將一直存在隊列中,開啓的方法很簡單:
application.properties上加上:
spring.rabbitmq.listener.simple.acknowledge-mode=manual
意爲改爲手動確認。
對於消費者端:
@RabbitHandler
public void onMessage(String str,Channel channel,Message message) throws IOException {
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); //手動調用
System.out.println(str);
}
手動調用下確認即可,消息就會被刪除。這一步可以放在業務邏輯的執行之後
生產者通知
對於生產者來說,這個消息是否推送成功完全不得知,所以生產者一方其實還有個消息回調的機制
在配置文件中:
@Bean
public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){
RabbitTemplate rabbitTemplate=new RabbitTemplate();
rabbitTemplate.setConnectionFactory(connectionFactory);
//設置開啓Mandatory,才能觸發回調函數,無論消息推送結果怎麼樣都強制調用回調函數
rabbitTemplate.setMandatory(true);
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("ConfirmCallback: "+"相關數據:"+correlationData);
System.out.println("ConfirmCallback: "+"確認情況:"+ack);
System.out.println("ConfirmCallback: "+"原因:"+cause);
}
});
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("ReturnCallback: "+"消息:"+message);
System.out.println("ReturnCallback: "+"迴應碼:"+replyCode);
System.out.println("ReturnCallback: "+"迴應信息:"+replyText);
System.out.println("ReturnCallback: "+"交換機:"+exchange);
System.out.println("ReturnCallback: "+"路由鍵:"+routingKey);
}
});
return rabbitTemplate;
}
主要是兩個回調,一個是confirm回調,一個是return回調,這兩個有什麼不同呢?
經檢驗得知,如果推送去一個不存在交換機上,那麼就會觸發confirm回調;如果推送去一個存在的交換機,但對應的路由鍵(或者說隊列)不存在,則會觸發return回調。瞭解了這個之後,就可以在該方法上根據具體的業務場景進行不同的操作了~