spring boot 最佳實踐(十一)--使用RabbitMQ

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