史上最簡明易懂的 rabbitmq 延時隊列(一)

基本思路:在一個普通的mq 隊列中設置死信交換器、死信路由鍵,然後把一個存放死信的隊列與死信交換器、死信路由鍵三者捆綁。普通隊列中設置消息過期時間,時間到了以後就會把消息發送到死信隊列裏面,等待消費者使用。具體代碼如下:
1.常量

package com.lyf.www.testDelayQqueue;

/**
 * @author liuyanfei
 * @description
 * @date 2020/4/11 10:59 PM
 **/
public class MqConstant {


    //正常 交換器、隊列、key
    public static final String ORDINARY_EXCHANGE = "ordinary_exchange";
    public static final String ORDINARY_ROUTEKEY = "ordinary_routekey";
    public static final String ORDINARY_QUEUE = "ordinary_queue";

    // 死信 交換器、隊列、key
    public static final String DEAD_EXCHANGE = "dead_exchange";
    public static final String DEAD_ROUTEKEY = "dead_routekey";
    public static final String DEAD_QUEUEU = "dead_queue";

}

2.配置文件

package com.lyf.www.testDelayQqueue;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class RabbitConfig {

    @Bean
    public DirectExchange ordinaryExchange() {
        return new DirectExchange(MqConstant.ORDINARY_EXCHANGE, true, false);
    }
    @Bean
    public Queue ordinaryQueue() {
        Map<String, Object> map = new HashMap<>();
        //把死信 交換器和路由鍵  綁定到普通隊列中
        map.put("x-dead-letter-exchange", MqConstant.DEAD_EXCHANGE);
        map.put("x-dead-letter-routing-key", MqConstant.DEAD_ROUTEKEY);
        Queue queue = new Queue(MqConstant.ORDINARY_QUEUE, true, false, false, map);
        return queue;
    }
    //把普通 交換器、隊列、路由鍵  綁定在一塊
    @Bean
    public Binding queueDeadBinding() {
        return BindingBuilder.bind(ordinaryQueue()).to(ordinaryExchange()).with(MqConstant.ORDINARY_ROUTEKEY);
    }





    //把死信 交換器、隊列、路由鍵  綁定在一塊
    @Bean
    public DirectExchange deadExchange() {
        return new DirectExchange(MqConstant.DEAD_EXCHANGE, true, false);
    }
    @Bean
    public Queue deadQueue() {
        return new Queue(MqConstant.DEAD_QUEUEU, true, false, false);
    }


    @Bean
    public Binding queueTransBinding() {
        return BindingBuilder.bind(deadQueue()).to(deadExchange()).with(MqConstant.DEAD_ROUTEKEY);
    }
}

3.生產者

package com.lyf.www.testDelayQqueue;

/**
 * @author liuyanfei
 * @description
 * @date 2020/4/11 9:03 PM
 **/
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service
public class Producr {

    @Autowired
    private AmqpTemplate amqpTemplate;

    public void send(String msg, long time) {
        //rabbit默認爲毫秒級
        long times = time * 1000;
        MessagePostProcessor processor = new MessagePostProcessor() {

            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setExpiration(String.valueOf(times));
                return message;
            }
        };


        amqpTemplate.convertAndSend(MqConstant.ORDINARY_EXCHANGE, MqConstant.ORDINARY_ROUTEKEY, msg, processor);
    }
}

4.消費者

package com.lyf.www.testDelayQqueue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.Date;


@Service
public class TradeProcess {
    private static final Logger LOGGER = LoggerFactory.getLogger(TradeProcess.class);

    @Autowired
    private AmqpTemplate amqpTemplate;

//注意此時監聽的是死信隊列
    @RabbitListener(queues=MqConstant.DEAD_QUEUEU)
    @RabbitHandler
    public void process(String content) {
        LOGGER.info("接收到延時消息:"+content);
    }
}

5.controller

package com.lyf.www.testDelayQqueue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.text.SimpleDateFormat;
import java.util.Date;


@RestController
@RequestMapping("/delayQueue")
public class DelayQueueController {

    private static final Logger LOGGER = LoggerFactory.getLogger(DelayQueueController.class);

    @Autowired
    private Producr producr;

    @GetMapping("/send/{time}")
    public String send(@PathVariable("time") int time){
         LOGGER.info("現在時間是:{},baby,你來吧我到了,你大概{}秒後能到達,我等你。。。。麼麼", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()),time);


        producr.send("我是你珊珊來遲的初戀女友,來啵一個。。。。", time);
        return "ok";
    }

}

6.application.properties

server.port=9002

#mq 配置
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest


7.maven

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>springboot_rabbitmq</groupId>
    <artifactId>springboot_rabbitmq</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8.1</version>
        </dependency>
    </dependencies>
</project>

瀏覽器執行:http://127.0.0.1:9002/delayQueue/send/5
運行結果:每次去約會 男士一定要先到幾秒等初戀女友,不能讓人家女孩子等你(敲黑板)。所以用延時隊列再合適不過了,
image
當然這樣寫 是爲了更方便理解。真實項目中還需要做下一步處理。不能我來個延時處理就弄一副上面的對應那一整套,這樣做是違揹人道的。 下節我將寫一個更接近於真實項目中的 demo

下面是一個悲傷的故事

由於開始做單元測試的時候死信交換器我就隨便叫了一個名字 比如"aaaa"。所以在運行了程序後,程序自動把這個 aaaa 綁定到了普通隊列上面。
開始時我定義的常量,對應上面的 1.常量

public class MqConstant {


    //正常 交換器、隊列、key
    public static final String ORDINARY_EXCHANGE = "ordinary_exchange";
    public static final String ORDINARY_ROUTEKEY = "ordinary_routekey";
    public static final String ORDINARY_QUEUE = "ordinary_queue";

    // 死信 交換器、隊列、key
    //此時我的 死信交換器是 aaaa
    public static final String DEAD_EXCHANGE = "aaaa";
    public static final String DEAD_ROUTEKEY = "dead_routekey";
    public static final String DEAD_QUEUEU = "dead_queue";

}

後來想起了當年被一個據說是阿里出來的大神面試,因爲類名首字母沒有大寫而被 pass 的慘痛經歷,我決定要從小事做起,命名定義變量一定要更規範,所以把 aaaa 改成"ordinary_exchange"。當然開始這是做了一個 demo 試試,很多寫的都不規範,當我全部都整改完以後,再次執行程序,結果。。。。
image
結果就是,我再也沒有等到我的初戀女友出現。熙熙攘攘的大街上,雙雙對對,而我的身邊空空蕩蕩,路過音像店門口播放着當年鄉村愛情的片尾曲:『是誰爲我穿上嫁妝,是誰伴我走進洞房,誰是我的新郎我是誰的新娘』。我猶如看見我的女友在哼着這首歌牽起來別人的手。。。。我再也抑制不住我的悲傷,舉起手對着老天喊:爲什麼。。。。
在我檢查了好幾遍變量是否定義錯誤,程序是否有問題後,多次實驗,女友終究還是沒有回來。此時已經臨晨兩點!!!爲什麼,爲什麼。。。。我的一切都沒有問題啊,爲什麼。此時程序啓動幾行小字引起了我的注意:
image
藍色的框框裏面全文是:

Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg 'x-dead-letter-exchange' for queue 'ordinary_queue' in vhost '/': received 'dead_exchange' but current is 'aaaa', class-id=50, method-id=10)

意思是:信道罷工了,ordinary_queue 這個隊列的 x-dead-letter-exchange這個屬性接收到的是dead_exchange,但是當前是 aaaa。什麼意思呢???再來一張圖
image

這是普通隊列(死信交換器綁定在他的身上)的信息,一切真相大白。普通隊列還綁定着我之前的那個死信交換器 aaaa,而我現在的死信交換器叫 dead_exchange。
我淚目了,原來女友從來就沒有變心,而是我變心了。她還傻傻的抱着我以前的相片等着我,而我得意於現在的新裝對她視而不見。
之所以會犯這個錯誤,是因爲被『Started App in 14.693 seconds (JVM running for 15.494)』這句話給矇蔽了,其實那堆小字前面的信息有個 大紅『Error』呢,我也沒看見。一般運行 java 程序,看見 start…seconds就萬事大吉了,但是使用中間件的時候,就一定要小心了,java 程序正確啓動起來,不一定中間件會配合你。

結局:

以現在電視劇的套路,我最後一定和女友沒羞沒臊的生活在了一起。但是現實就是現實,題目說了這是個悲傷的故事。 由於我的固執,始終不肯再變回原來的樣子,所以初戀女友傷心的離開了我。
後來我把關於初戀女友的所有付之一炬當然包括普通隊列(ordinary_queue),再次啓動程序一切正常。然後也能收到延時消息了
image
當然來約會的也不再是初戀女友了。。。。。

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