[Article] with RabbitMQ RabbitMQ you get delayed queue

This article flavors: fish-flavored pork is expected to read: 10 minutes

I. Description

In the last article, we describe what RabbitMQ in the dead letter queue is when to use and how to use RabbitMQ dead letter queue. I believe that through learning on one, for the dead letter queue already have more understanding, this one is also closely linked with the contents of the dead letter queue, if you do not know the dead letter queue, it is recommended that you first carry out a previous article reading.

This one, we will continue to introduce advanced features RabbitMQ through Benpian study, you will gain:

  1. What is the delay queue
  2. Latency Queuing usage scenarios
  3. RabbitMQ in TTL
  4. RabbitMQ how to implement the delay queue

Second, this paper outlines

The following is the outline of this article:

1.png

Before reading this article, you need to have a simple understanding of the RabbitMQ and dead letter queue.

Third, what is the delay queue

延时队列First, it is a queue, the queue element is internal means that 有序the elements are enqueuing and dequeuing directional element enters from one end, removed from the other end.

Secondly, 延时队列the most important feature of it is reflected in 延时the property, with the ordinary queue is not the same, 普通队列中的元素总是等着希望被早点取出处理,而延时队列中的元素则是希望被在指定时间得到取出和处理so the delay queue element is the time are with the property, usually is the message needs to be processed or task.

Briefly, the queue is used to store the delay elements need to be processed at a specified time queue.

Fourth, the delay queue usage scenarios

So when you need to use the delay queue? Consider the following scenario:

  1. Unpaid order will be automatically canceled within 10 minutes.
  2. Shop newly created, if not uploaded merchandise within 10 days will automatically send a message reminder.
  3. The bill is not paid within one week automatically clearing.
  4. User registration is successful, within three days if there is no landing is carried out SMS alerts.
  5. Users initiate a refund, if not treated within three days of notification of the relevant operational staff.
  6. After the scheduled meeting, we need to inform the various participants to attend the meeting at the scheduled time points ten minutes ago.

These scenes have a feature you need to specify a point in time before or after an event occurs complete a specific task, such as: order generation event, check the status of the payment order after ten minutes, and then the orders were not paid closed; occurrence shop to create an event, check the number of new products on the shop ten days later, and then notice the new number of merchants 0; bill generation event, check the bill payment status, and then automatically clearing unpaid orders; new user registration events three days after checking newly registered user activity data, and then notify the user does not record any activity; refund the incident, check whether the order has been processed in three days, if not yet been processed, a message is sent to the relevant operational staff ; occurrence of a predetermined event meeting to determine whether the meeting starts only ten minutes, and if so, to inform the individual participants.

Seems a timed task, has been polling data, check once per second, remove the need for data to be processed, then the process does not get away with it? If the data was relatively low, we can really do this, for example: for the "If you do not pay the bill within the week for automatic settlement" such a demand, if not strictly limited to the time, but Week on the loose sense, then run a nightly cron job to check all bills unpaid, indeed, a viable option. But for the large amount of data, and timeliness of strong scenes, such as: "Order is not paid within ten minutes off" orders not to pay in the short term there may be many, and even reach one million or even tens during the event level, on the way such a large amount of data still use polling is obviously not desirable, it may not be completed within one second to check all orders, while the database will bring a lot of pressure, unable to meet the business requirements and performs poorly .

More important point is, do not! excellent! elegant!

Yes, as a pursuit of programmers, it should always be the pursuit of a more elegant and more elegant code architecture style, to write code like beautiful poetry. 【funny】

At this time, the delay queue will be the debut, the above scenario, it is useless delay queue.

Since it 延时队列can solve many specific scenes, the mission requirements with a time attribute, how to construct a delay queue it? Next, the article describes how to implement the delay with RabbitMQ queue.

Five, RabbitMQ in TTL

Before introducing the delay queue, you need to first introduce an advanced feature of RabbitMQ - TTL(Time To Live).

TTLwhat is it then? TTLRabbitMQ is a message or attribute in the queue, indicating 一条消息或者该队列中的所有消息的最大存活时间milliseconds. In other words, if a message or set the TTL attribute set to enter the TTL property of a queue, so if this message is not to be consumed within the time TTL settings, it will become a "dead letter" (As for what a dead letter, please look at the previous post). If both TTL and message queues TTL, then the smaller value will be used.

So, how to set the TTL value of it? There are two ways, the first one is set "x-message-ttl" attribute in the queue when the queue is created as follows:

Map<String, Object> args = new HashMap<String, Object>();
args.put("x-message-ttl", 6000);
channel.queueDeclare(queueName, durable, exclusive, autoDelete, args);

All this is delivered to the queue of messages will not survive more than a maximum of 6s.

Another way is to set the TTL for each message, as follows:

AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
builder.expiration("6000");
AMQP.BasicProperties properties = builder.build();
channel.basicPublish(exchangeName, routingKey, mandatory, properties, "msg body".getBytes());

Such expiration time this message has been set to 6s.

But there is a difference in two ways, if the queue TTL property is set, then once the message expires, the queue will be discarded, and the second way, even if the message expires, will not necessarily be immediately discarded, because the message is expiration is determined immediately before delivery to the consumer, if the current queue has a serious message backlog, already overdue message might be able to survive longer.

In addition, the point to note is that if you do not set the TTL, indicates that the message never expires, if the TTL is set to 0, it means that unless the message can now be delivered directly to the consumer, otherwise the message will be discarded.

Sixth, how to use RabbitMQ achieve Latency Queuing

The previous was introduced if the dead letter queue, previously, he also introduced the TTL, so far, the use RabbitMQ achieve delayed two elements of the queue already collect, then they only need to be reconciled, then add a little seasoning delay queue can be freshly baked.

Think about it 延时队列, not that you want to message delay how long it is processed, TTL is just to make news after a long delay become dead letter, on the other hand, become a dead letter of the message will be delivered to the dead letter queue, so that only consumers need to consume has been dead letter queue messages everything will be fine, because there is hope that the message is a message immediately processed.

From the figure can be seen to flow substantially message:

23.png

生产者生产一条延时消息,根据需要延时时间的不同,利用不同的routingkey将消息路由到不同的延时队列,每个队列都设置了不同的TTL属性,并绑定在同一个死信交换机中,消息过期后,根据routingkey的不同,又会被路由到不同的死信队列中,消费者只需要监听对应的死信队列进行处理即可。

下面来看代码:

先声明交换机、队列以及他们的绑定关系:

@Configuration
public class RabbitMQConfig {

    public static final String DELAY_EXCHANGE_NAME = "delay.queue.demo.business.exchange";
    public static final String DELAY_QUEUEA_NAME = "delay.queue.demo.business.queuea";
    public static final String DELAY_QUEUEA_ROUTING_KEY = "delay.queue.demo.business.queuea.routingkey";
    public static final String DELAY_QUEUEB_ROUTING_KEY = "delay.queue.demo.business.queueb.routingkey";
    public static final String DEAD_LETTER_EXCHANGE = "delay.queue.demo.deadletter.exchange";
    public static final String DEAD_LETTER_QUEUEA_ROUTING_KEY = "delay.queue.demo.deadletter.delay_10s.routingkey";
    public static final String DEAD_LETTER_QUEUEB_ROUTING_KEY = "delay.queue.demo.deadletter.delay_60s.routingkey";
    public static final String DEAD_LETTER_QUEUEA_NAME = "delay.queue.demo.deadletter.queuea";
    public static final String DEAD_LETTER_QUEUEB_NAME = "delay.queue.demo.deadletter.queueb";

    // 声明延时Exchange
    @Bean("delayExchange")
    public DirectExchange delayExchange(){
        return new DirectExchange(DELAY_EXCHANGE_NAME);
    }

    // 声明死信Exchange
    @Bean("deadLetterExchange")
    public DirectExchange deadLetterExchange(){
        return new DirectExchange(DEAD_LETTER_EXCHANGE);
    }

    // 声明延时队列A 延时10s
    // 并绑定到对应的死信交换机
    @Bean("delayQueueA")
    public Queue delayQueueA(){
        Map<String, Object> args = new HashMap<>(2);
        // x-dead-letter-exchange    这里声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
        // x-dead-letter-routing-key  这里声明当前队列的死信路由key
        args.put("x-dead-letter-routing-key", DEAD_LETTER_QUEUEA_ROUTING_KEY);
        // x-message-ttl  声明队列的TTL
        args.put("x-message-ttl", 6000);
        return QueueBuilder.durable(DEAD_LETTER_QUEUEA_NAME).withArguments(args).build();
    }

    // 声明延时队列B 延时 60s
    // 并绑定到对应的死信交换机
    @Bean("delayQueueB")
    public Queue delayQueueB(){
        Map<String, Object> args = new HashMap<>(2);
        // x-dead-letter-exchange    这里声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
        // x-dead-letter-routing-key  这里声明当前队列的死信路由key
        args.put("x-dead-letter-routing-key", DEAD_LETTER_QUEUEB_ROUTING_KEY);
        // x-message-ttl  声明队列的TTL
        args.put("x-message-ttl", 60000);
        return QueueBuilder.durable(DEAD_LETTER_QUEUEB_NAME).withArguments(args).build();
    }

    // 声明死信队列A 用于接收延时10s处理的消息
    @Bean("deadLetterQueueA")
    public Queue deadLetterQueueA(){
        return new Queue(DEAD_LETTER_QUEUEA_NAME);
    }

    // 声明死信队列B 用于接收延时60s处理的消息
    @Bean("deadLetterQueueB")
    public Queue deadLetterQueueB(){
        return new Queue(DEAD_LETTER_QUEUEB_NAME);
    }

    // 声明延时队列A绑定关系
    @Bean
    public Binding delayBindingA(@Qualifier("delayQueueA") Queue queue,
                                    @Qualifier("delayExchange") DirectExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(DELAY_QUEUEA_ROUTING_KEY);
    }

    // 声明业务队列B绑定关系
    @Bean
    public Binding delayBindingB(@Qualifier("delayQueueB") Queue queue,
                                    @Qualifier("delayExchange") DirectExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(DELAY_QUEUEB_ROUTING_KEY);
    }

    // 声明死信队列A绑定关系
    @Bean
    public Binding deadLetterBindingA(@Qualifier("deadLetterQueueA") Queue queue,
                                    @Qualifier("deadLetterExchange") DirectExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(DEAD_LETTER_QUEUEA_ROUTING_KEY);
    }

    // 声明死信队列B绑定关系
    @Bean
    public Binding deadLetterBindingB(@Qualifier("deadLetterQueueB") Queue queue,
                                      @Qualifier("deadLetterExchange") DirectExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(DEAD_LETTER_QUEUEB_ROUTING_KEY);
    }
}

接下来,创建两个消费者,分别对两个死信队列的消息进行消费:

@Slf4j
@Component
public class DeadLetterQueueConsumer {

    @RabbitListener(queues = DEAD_LETTER_QUEUEA_NAME)
    public void receiveA(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody());
        log.info("当前时间:{},死信队列A收到消息:{}", new Date().toString(), msg);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }

    @RabbitListener(queues = DEAD_LETTER_QUEUEB_NAME)
    public void receiveB(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody());
        log.info("当前时间:{},死信队列B收到消息:{}", new Date().toString(), msg);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
}

然后是消息的生产者:

@Component
public class DelayMessageSender {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendMsg(String msg, DelayTypeEnum type){
        switch (type){
            case DELAY_10s:
                rabbitTemplate.convertAndSend(DELAY_EXCHANGE_NAME, DELAY_QUEUEA_ROUTING_KEY, msg);
                break;
            case DELAY_60s:
                rabbitTemplate.convertAndSend(DELAY_EXCHANGE_NAME, DELAY_QUEUEB_ROUTING_KEY, msg);
                break;
        }
    }
}

接下来,我们暴露一个web接口来生产消息:

@Slf4j
@RequestMapping("rabbitmq")
@RestController
public class RabbitMQMsgController {

    @Autowired
    private DelayMessageSender sender;

    @RequestMapping("sendmsg")
    public void sendMsg(String msg, Integer delayType){
        log.info("当前时间:{},收到请求,msg:{},delayType:{}", new Date(), msg, delayType);
        sender.sendMsg(msg, Objects.requireNonNull(DelayTypeEnum.getDelayTypeEnumByValue(delayType)));
    }
}

准备就绪,启动!

打开rabbitMQ的管理后台,可以看到我们刚才创建的交换机和队列信息:

2.png

4.png

3.png

接下来,我们来发送几条消息,http://localhost:8080/rabbitmq/sendmsg?msg=testMsg1&delayType=1 http://localhost:8080/rabbitmq/sendmsg?msg=testMsg2&delayType=2

日志如下:

2019-07-28 16:02:19.813  INFO 3860 --- [nio-8080-exec-9] c.m.d.controller.RabbitMQMsgController   : 当前时间:Sun Jul 28 16:02:19 CST 2019,收到请求,msg:testMsg1,delayType:1
2019-07-28 16:02:19.815  INFO 3860 --- [nio-8080-exec-9] .l.DirectReplyToMessageListenerContainer : SimpleConsumer [queue=amq.rabbitmq.reply-to, consumerTag=amq.ctag-o-qPpkWIkRm73DIrOIVhig identity=766339] started
2019-07-28 16:02:25.829  INFO 3860 --- [ntContainer#1-1] c.m.d.mq.DeadLetterQueueConsumer         : 当前时间:Sun Jul 28 16:02:25 CST 2019,死信队列A收到消息:testMsg1
2019-07-28 16:02:41.326  INFO 3860 --- [nio-8080-exec-1] c.m.d.controller.RabbitMQMsgController   : 当前时间:Sun Jul 28 16:02:41 CST 2019,收到请求,msg:testMsg2,delayType:2
2019-07-28 16:03:41.329  INFO 3860 --- [ntContainer#0-1] c.m.d.mq.DeadLetterQueueConsumer         : 当前时间:Sun Jul 28 16:03:41 CST 2019,死信队列B收到消息:testMsg2

第一条消息在6s后变成了死信消息,然后被消费者消费掉,第二条消息在60s之后变成了死信消息,然后被消费掉,这样,一个还算ok的延时队列就打造完成了。

不过,等等,如果这样使用的话,岂不是每增加一个新的时间需求,就要新增一个队列,这里只有6s和60s两个时间选项,如果需要一个小时后处理,那么就需要增加TTL为一个小时的队列,如果是预定会议室然后提前通知这样的场景,岂不是要增加无数个队列才能满足需求??

嗯,仔细想想,事情并不简单。

七、RabbitMQ延时队列优化

显然,需要一种更通用的方案才能满足需求,那么就只能将TTL设置在消息属性里了。我们来试一试。

增加一个延时队列,用于接收设置为任意延时时长的消息,增加一个相应的死信队列和routingkey:

@Configuration
public class RabbitMQConfig {

    public static final String DELAY_EXCHANGE_NAME = "delay.queue.demo.business.exchange";
    public static final String DELAY_QUEUEC_NAME = "delay.queue.demo.business.queuec";
    public static final String DELAY_QUEUEC_ROUTING_KEY = "delay.queue.demo.business.queuec.routingkey";
    public static final String DEAD_LETTER_EXCHANGE = "delay.queue.demo.deadletter.exchange";
    public static final String DEAD_LETTER_QUEUEC_ROUTING_KEY = "delay.queue.demo.deadletter.delay_anytime.routingkey";
    public static final String DEAD_LETTER_QUEUEC_NAME = "delay.queue.demo.deadletter.queuec";

    // 声明延时Exchange
    @Bean("delayExchange")
    public DirectExchange delayExchange(){
        return new DirectExchange(DELAY_EXCHANGE_NAME);
    }

    // 声明死信Exchange
    @Bean("deadLetterExchange")
    public DirectExchange deadLetterExchange(){
        return new DirectExchange(DEAD_LETTER_EXCHANGE);
    }

    // 声明延时队列C 不设置TTL
    // 并绑定到对应的死信交换机
    @Bean("delayQueueC")
    public Queue delayQueueC(){
        Map<String, Object> args = new HashMap<>(3);
        // x-dead-letter-exchange    这里声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
        // x-dead-letter-routing-key  这里声明当前队列的死信路由key
        args.put("x-dead-letter-routing-key", DEAD_LETTER_QUEUEC_ROUTING_KEY);
        return QueueBuilder.durable(DELAY_QUEUEC_NAME).withArguments(args).build();
    }

    // 声明死信队列C 用于接收延时任意时长处理的消息
    @Bean("deadLetterQueueC")
    public Queue deadLetterQueueC(){
        return new Queue(DEAD_LETTER_QUEUEC_NAME);
    }

    // 声明延时列C绑定关系
    @Bean
    public Binding delayBindingC(@Qualifier("delayQueueC") Queue queue,
                                 @Qualifier("delayExchange") DirectExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(DELAY_QUEUEC_ROUTING_KEY);
    }

    // 声明死信队列C绑定关系
    @Bean
    public Binding deadLetterBindingC(@Qualifier("deadLetterQueueC") Queue queue,
                                      @Qualifier("deadLetterExchange") DirectExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(DEAD_LETTER_QUEUEC_ROUTING_KEY);
    }
}

增加一个死信队列C的消费者:

@RabbitListener(queues = DEAD_LETTER_QUEUEC_NAME)
public void receiveC(Message message, Channel channel) throws IOException {
    String msg = new String(message.getBody());
    log.info("当前时间:{},死信队列C收到消息:{}", new Date().toString(), msg);
    channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}

再次启动!然后访问:http://localhost:8080/rabbitmq/delayMsg?msg=testMsg1delayTime=5000 来生产消息,注意这里的单位是毫秒。

2019-07-28 16:45:07.033  INFO 31468 --- [nio-8080-exec-4] c.m.d.controller.RabbitMQMsgController   : 当前时间:Sun Jul 28 16:45:07 CST 2019,收到请求,msg:testMsg1,delayTime:5000
2019-07-28 16:45:11.694  INFO 31468 --- [nio-8080-exec-5] c.m.d.controller.RabbitMQMsgController   : 当前时间:Sun Jul 28 16:45:11 CST 2019,收到请求,msg:testMsg2,delayTime:5000
2019-07-28 16:45:12.048  INFO 31468 --- [ntContainer#1-1] c.m.d.mq.DeadLetterQueueConsumer         : 当前时间:Sun Jul 28 16:45:12 CST 2019,死信队列C收到消息:testMsg1
2019-07-28 16:45:16.709  INFO 31468 --- [ntContainer#1-1] c.m.d.mq.DeadLetterQueueConsumer         : 当前时间:Sun Jul 28 16:45:16 CST 2019,死信队列C收到消息:testMsg2

看起来似乎没什么问题,但不要高兴的太早,在最开始的时候,就介绍过,如果使用在消息属性上设置TTL的方式,消息可能并不会按时“死亡“,因为RabbitMQ只会检查第一个消息是否过期,如果过期则丢到死信队列,索引如果第一个消息的延时时长很长,而第二个消息的延时时长很短,则第二个消息并不会优先得到执行。

实验一下:

2019-07-28 16:49:02.957  INFO 31468 --- [nio-8080-exec-8] c.m.d.controller.RabbitMQMsgController   : 当前时间:Sun Jul 28 16:49:02 CST 2019,收到请求,msg:longDelayedMsg,delayTime:20000
2019-07-28 16:49:10.671  INFO 31468 --- [nio-8080-exec-9] c.m.d.controller.RabbitMQMsgController   : 当前时间:Sun Jul 28 16:49:10 CST 2019,收到请求,msg:shortDelayedMsg,delayTime:2000
2019-07-28 16:49:22.969  INFO 31468 --- [ntContainer#1-1] c.m.d.mq.DeadLetterQueueConsumer         : 当前时间:Sun Jul 28 16:49:22 CST 2019,死信队列C收到消息:longDelayedMsg
2019-07-28 16:49:22.970  INFO 31468 --- [ntContainer#1-1] c.m.d.mq.DeadLetterQueueConsumer         : 当前时间:Sun Jul 28 16:49:22 CST 2019,死信队列C收到消息:shortDelayedMsg

我们先发了一个延时时长为20s的消息,然后发了一个延时时长为2s的消息,结果显示,第二个消息会在等第一个消息成为死信后才会“死亡“。

八、利用RabbitMQ插件实现延迟队列

上文中提到的问题,确实是一个硬伤,如果不能实现在消息粒度上添加TTL,并使其在设置的TTL时间及时死亡,就无法设计成一个通用的延时队列。

那如何解决这个问题呢?不要慌,安装一个插件即可:https://www.rabbitmq.com/community-plugins.html ,下载rabbitmq_delayed_message_exchange插件,然后解压放置到RabbitMQ的插件目录。

接下来,进入RabbitMQ的安装目录下的sbin目录,执行下面命令让该插件生效,然后重启RabbitMQ。

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

然后,我们再声明几个Bean:

@Configuration
public class DelayedRabbitMQConfig {
    public static final String DELAYED_QUEUE_NAME = "delay.queue.demo.delay.queue";
    public static final String DELAYED_EXCHANGE_NAME = "delay.queue.demo.delay.exchange";
    public static final String DELAYED_ROUTING_KEY = "delay.queue.demo.delay.routingkey";

    @Bean
    public Queue immediateQueue() {
        return new Queue(DELAYED_QUEUE_NAME);
    }

    @Bean
    public CustomExchange customExchange() {
        Map<String, Object> args = new HashMap<>();
        args.put("x-delayed-type", "direct");
        return new CustomExchange(DELAYED_EXCHANGE_NAME, "x-delayed-message", true, false, args);
    }

    @Bean
    public Binding bindingNotify(@Qualifier("immediateQueue") Queue queue,
                                 @Qualifier("customExchange") CustomExchange customExchange) {
        return BindingBuilder.bind(queue).to(customExchange).with(DELAYED_ROUTING_KEY).noargs();
    }
}

controller层再添加一个入口:

@RequestMapping("delayMsg2")
public void delayMsg2(String msg, Integer delayTime) {
    log.info("当前时间:{},收到请求,msg:{},delayTime:{}", new Date(), msg, delayTime);
    sender.sendDelayMsg(msg, delayTime);
}

消息生产者的代码也需要修改:

public void sendDelayMsg(String msg, Integer delayTime) {
    rabbitTemplate.convertAndSend(DELAYED_EXCHANGE_NAME, DELAYED_ROUTING_KEY, msg, a ->{
        a.getMessageProperties().setDelay(delayTime);
        return a;
    });
}

最后,再创建一个消费者:

@RabbitListener(queues = DELAYED_QUEUE_NAME)
public void receiveD(Message message, Channel channel) throws IOException {
    String msg = new String(message.getBody());
    log.info("当前时间:{},延时队列收到消息:{}", new Date().toString(), msg);
    channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}

一切准备就绪,启动!然后分别访问以下链接:

http://localhost:8080/rabbitmq/delayMsg2?msg=msg1&delayTime=20000
http://localhost:8080/rabbitmq/delayMsg2?msg=msg2&delayTime=2000

日志如下:

2019-07-28 17:28:13.729  INFO 25804 --- [nio-8080-exec-2] c.m.d.controller.RabbitMQMsgController   : 当前时间:Sun Jul 28 17:28:13 CST 2019,收到请求,msg:msg1,delayTime:20000
2019-07-28 17:28:20.607  INFO 25804 --- [nio-8080-exec-1] c.m.d.controller.RabbitMQMsgController   : 当前时间:Sun Jul 28 17:28:20 CST 2019,收到请求,msg:msg2,delayTime:2000
2019-07-28 17:28:22.624  INFO 25804 --- [ntContainer#1-1] c.m.d.mq.DeadLetterQueueConsumer         : 当前时间:Sun Jul 28 17:28:22 CST 2019,延时队列收到消息:msg2
2019-07-28 17:28:33.751  INFO 25804 --- [ntContainer#1-1] c.m.d.mq.DeadLetterQueueConsumer         : 当前时间:Sun Jul 28 17:28:33 CST 2019,延时队列收到消息:msg1

第二个消息被先消费掉了,符合预期。至此,RabbitMQ实现延时队列的部分就完结了。

九、总结

Queuing delay is useful in scenarios require a delay process, the delay is achieved using RabbitMQ queue can be a good use of the characteristics of RabbitMQ, such as: Reliable message transmission, reliable message delivery, dead letter queue message is to protect the consumer at least once message not correctly processed and not discarded. Further, the characteristic RabbitMQ cluster can solve single point of failure, will not lead to a single node hang with lost or delayed message queue is unavailable.

Of course, the delay queue there are many other options, such as the use of Java DelayQueu, use zset Redis, the use Quartz or use kafka time round, these methods have their own characteristics, but like Hearthstone legendary general, this knowledge is like the hand in the card, the more you know, the more you can use the cards, a problem will be able to cope, it requires a lot of knowledge and experience accumulated reserves in order to create a better combination of cards so that they solve the problem the ability to get a better upgrade.

But on the other hand, with the growth of the passage of time and experience, and feel more and more of their limited ability, unable to face the complex and changing business needs alone, you need other people to help in many ways good mission accomplished. Also know Road have been heard, industry specializing in surgery, will not be arrogant, that he could get everything done, but also the center of gravity slowly shifted to how effective teamwork up, I believe that a highly coordinated team is always fighting to be more valuable than a person.

It took a week to complete this article, the paper all the code uploaded to github, https: //github.com/MFrank2016/delayed-queue-demo if necessary, can own inspection, hoping to help you, If you have the wrong place, please correct me, I also welcome the attention of the public number for message exchange.

TIM picture 20190714173105.png

Guess you like

Origin www.cnblogs.com/mfrank/p/11260355.html