SpringBoot整合RabbitMQ之交換機,確認與通知,持久化

梗概

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回調。瞭解了這個之後,就可以在該方法上根據具體的業務場景進行不同的操作了~

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