Quick Start
RabbitMQ是AMQP協議的一個實現,spring boot提供了快速的接入方案.參考
配置build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-amqp'
testImplementation 'org.springframework.amqp:spring-rabbit-test'
}
配置application.yml
spring:
rabbitmq:
host: 192.168.56.101
username: admin
password: pw
virtual-host: first_vhost
發送消息
public class FireEventController {
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/event")
public void visit(@RequestParam("name") String name) {
rabbitTemplate.convertAndSend("spring-boot-exchange", "foo.bar.baz",name);
}
}
接收消息
我們可以直接使用@RabbitListener註解聲明MQ消費者,同springMVC一樣,spring可以自動幫我們處理參數轉換的工作.
@RabbitListener(queues = "spring-boot")
public void receive(String message){
System.out.println("收到消息:"+ message);
}
生產級消息隊列配置
AMQP模型在觀察者模式基礎上多做了一層抽象“交換器 ”(Exchange). 在合理配置RabbitMQ時,我們需要正確理解AMQP的概念.參考
AMQP重要的概念有:虛擬主機,交換機,隊列,和綁定。
- 虛擬主機:一個虛擬主機持有一組交換機、隊列和綁定。爲什麼需要多個虛擬主機呢?很簡單,RabbitMQ當中,用戶只能在虛擬主機的粒度進行權限控制。 因此,如果需要禁止A組訪問B組的交換機/隊列/綁定,必須爲A和B分別創建一個虛擬主機。每一個RabbitMQ服務器都有一個默認的虛擬主機“/”。
- 交換機:Exchange 用於轉發消息,但是它不會做存儲 ,如果沒有 Queue bind 到 Exchange 的話,它會直接丟棄掉 Producer 發送過來的消息。 這裏有一個比較重要的概念:路由鍵 。消息到交換機的時候,交互機會轉發到對應的隊列中,那麼究竟轉發到哪個隊列,就要根據該路由鍵。
- 綁定:也就是交換機需要和隊列相綁定,這其中如下圖所示,是多對多的關係。
理想情況下,我們需要一個消息隊列做信息解耦,配置一個隊列(Queue)就夠了,生產者向這個隊列發送消息,消費者訂閱這個隊列消費.但在實際生存環境,爲了保證消息的不丟失,我們應該針對一些異常場景做一系列的配置.
參考
-
exchange: 配置業務交換機和備用交換機
屬性 業務交換機 AE交換機 死信交換機 Name exchange.bizTopic exchange.bizTopic.fanout exchange.bizTopic.dlx Type topic fanout topic Durability Durable Durable Durable Auto delete no no no Arguments “alternate-exchange” = “exchange.bizTopic.fanout” -
queue:配置消息隊列
屬性 業務隊列 AE隊列 死信隊列 Name queue.bizTopic.case1 queue.bizTopic.fanout queue.bizTopic.dlx Durability Durable Durable Durable Auto delete no no no Arguments “x-dead-letter-exchange” = “exchange.bizTopic.dlx” 一個消費場景一個消費隊列,如果有多個場景消費,建議配置多個業務隊列.可以基於發送的routingKey分發到不同的隊列,無法投遞的轉到AE隊列.死信隊列根據對錯誤的關注程度不同,可以統一輸出到一個死信隊列,或者不同的業務隊列輸出到對應的死信隊列.
-
binding:配置exchange路由
屬性 業務路由 AE路由 死信路由 Exchange exchange.bizTopic exchange.bizTopic.fanout exchange.bizTopic.dlx To queue queue.bizTopic.case1 queue.bizTopic.fanout queue.bizTopic.dlx Routing key queue.bizTopic.case1 # #
可靠消息投遞
在大多數互聯網消息解耦的場景下,以上方案可以實現99%的消息送達和消費.但在業務一致性和完整性要求非常高的情況下,1%的消息丟失也是不可接受的.我們需要對消息的送達和消費做更嚴格的管理.
配置消息發送到交換機確認機制:
spring:
rabbitmq:
publisher-confirms: true # 消息發送到交換機確認機制,是否確認回調
publisher-returns: true # 消息發送到交換機確認機制,是否返回回饋
template.mandatory: true
申明消息回調handler:
@PostConstruct
public void init() {
rabbitTemplate.setConfirmCallback(confirmCallback);
rabbitTemplate.setReturnCallback(returnCallback);
}
public void publishCreated(Order order) {
rabbitTemplate.convertAndSend(properties.bizExchange(), properties.createRoutingKey(), order,
new CorrelationData("" + order.getOrderId()));
}
當消息發送到交換機(exchange)時,MsgSendConfirmCallBack#confirm()被調用.
- 1.如果消息沒有到exchange,則 ack=false
- 2.如果消息到達exchange,則 ack=true
public class MsgSendConfirmCallBack implements RabbitTemplate.ConfirmCallback {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
log.info("MsgSendConfirmCallBack , 回調correlationData:" + correlationData);
if (ack) {
log.info("消息發送到exchange成功");
// TODO 刪除 msgId 與 Message 的關係
} else {
log.info("消息發送到exchange失敗");
// TODO 消息發送到exchange失敗 , 重新發送
}
}
}
當消息從交換機到隊列失敗時,MsgSendReturnCallback#returnedMessage()被調用。(若成功,則不調用)
**需要注意的是:該方法調用後,{@link MsgSendConfirmCallBack}中的confirm方法也會被調用,且ack = true **
public class MsgSendReturnCallback implements RabbitTemplate.ReturnCallback {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.info("MsgSendReturnCallback [消息從交換機到隊列失敗] message:" + message);
// TODO 消息從交換機到隊列失敗,重新發送
}
}
可靠消息消費
Spring boot 中進行 AOP攔截 自動幫助做重試,消息會自動重試參考.爲了防止死循環,我們需要做一些配置.參考
spring:
rabbitmq:
listener:
simple:
retry:
####開啓消費者重試,默認開啓
enabled: true
####最大重試次數(默認無數次)
max-attempts: 2
####重試間隔
initial-interval: 1