RabbitMQ延迟队列及消息延迟推送实现详解

 更新时间:2019年12月10日 10:32:28   转载 作者:海向  
这篇文章主要介绍了RabbitMQ延迟队列及消息延迟推送实现详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

这篇文章主要介绍了RabbitMQ延迟队列及消息延迟推送实现详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

应用场景

目前常见的应用软件都有消息的延迟推送的影子,应用也极为广泛,例如:

  • 淘宝七天自动确认收货。在我们签收商品后,物流系统会在七天后延时发送一个消息给支付系统,通知支付系统将款打给商家,这个过程持续七天,就是使用了消息中间件的延迟推送功能。
  • 12306 购票支付确认页面。我们在选好票点击确定跳转的页面中往往都会有倒计时,代表着 30 分钟内订单不确认的话将会自动取消订单。其实在下订单那一刻开始购票业务系统就会发送一个延时消息给订单系统,延时30分钟,告诉订单系统订单未完成,如果我们在30分钟内完成了订单,则可以通过逻辑代码判断来忽略掉收到的消息。

在上面两种场景中,如果我们使用下面两种传统解决方案无疑大大降低了系统的整体性能和吞吐量:

  • 使用 redis 给订单设置过期时间,最后通过判断 redis 中是否还有该订单来决定订单是否已经完成。这种解决方案相较于消息的延迟推送性能较低,因为我们知道 redis 都是存储于内存中,我们遇到恶意下单或者刷单的将会给内存带来巨大压力。
  • 使用传统的数据库轮询来判断数据库表中订单的状态,这无疑增加了IO次数,性能极低。
  • 使用 jvm 原生的 DelayQueue ,也是大量占用内存,而且没有持久化策略,系统宕机或者重启都会丢失订单信息。

消息延迟推送的实现

在 RabbitMQ 3.6.x 之前我们一般采用死信队列+TTL过期时间来实现延迟队列,我们这里不做过多介绍,可以参考之前文章来了解:TTL、死信队列

在 RabbitMQ 3.6.x 开始,RabbitMQ 官方提供了延迟队列的插件,可以下载放置到 RabbitMQ 根目录下的 plugins 下。延迟队列插件下载

首先我们创建交换机和消息队列

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

@Configuration
public class MQConfig {

  public static final String LAZY_EXCHANGE = "Ex.LazyExchange";
  public static final String LAZY_QUEUE = "MQ.LazyQueue";
  public static final String LAZY_KEY = "lazy.#";

  @Bean
  public TopicExchange lazyExchange(){
    //Map<String, Object> pros = new HashMap<>();
    //设置交换机支持延迟消息推送
    //pros.put("x-delayed-message", "topic");
    TopicExchange exchange = new TopicExchange(LAZY_EXCHANGE, true, false, pros);
    exchange.setDelayed(true);
    return exchange;
  }

  @Bean
  public Queue lazyQueue(){
    return new Queue(LAZY_QUEUE, true);
  }

  @Bean
  public Binding lazyBinding(){
    return BindingBuilder.bind(lazyQueue()).to(lazyExchange()).with(LAZY_KEY);
  }
}

我们在 Exchange 的声明中可以设置exchange.setDelayed(true)来开启延迟队列,也可以设置为以下内容传入交换机声明的方法中,因为第一种方式的底层就是通过这种方式来实现的。

    //Map<String, Object> pros = new HashMap<>();
    //设置交换机支持延迟消息推送
    //pros.put("x-delayed-message", "topic");
    TopicExchange exchange = new TopicExchange(LAZY_EXCHANGE, true, false, pros);

发送消息时我们需要指定延迟推送的时间,我们这里在发送消息的方法中传入参数 new MessagePostProcessor() 是为了获得 Message对象,因为需要借助 Message对象的api 来设置延迟时间。

import com.anqi.mq.config.MQConfig;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class MQSender {

  @Autowired
  private RabbitTemplate rabbitTemplate;

  //confirmCallback returnCallback 代码省略,请参照上一篇
 
  public void sendLazy(Object message){
    rabbitTemplate.setMandatory(true);
    rabbitTemplate.setConfirmCallback(confirmCallback);
    rabbitTemplate.setReturnCallback(returnCallback);
    //id + 时间戳 全局唯一
    CorrelationData correlationData = new CorrelationData("12345678909"+new Date());

    //发送消息时指定 header 延迟时间
    rabbitTemplate.convertAndSend(MQConfig.LAZY_EXCHANGE, "lazy.boot", message,
        new MessagePostProcessor() {
      @Override
      public Message postProcessMessage(Message message) throws AmqpException {
        //设置消息持久化
        message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
        //message.getMessageProperties().setHeader("x-delay", "6000");
        message.getMessageProperties().setDelay(6000);
        return message;
      }
    }, correlationData);
  }
}

我们可以观察 setDelay(Integer i)底层代码,也是在 header 中设置 x-delay。等同于我们手动设置 header

message.getMessageProperties().setHeader("x-delay", "6000");

/**
 * Set the x-delay header.
 * @param delay the delay.
 * @since 1.6
 */
public void setDelay(Integer delay) {
  if (delay == null || delay < 0) {
    this.headers.remove(X_DELAY);
  }
  else {
    this.headers.put(X_DELAY, delay);
  }
}

消费端进行消费

import com.rabbitmq.client.Channel;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Map;

@Component
public class MQReceiver {

  @RabbitListener(queues = "MQ.LazyQueue")
  @RabbitHandler
  public void onLazyMessage(Message msg, Channel channel) throws IOException{
    long deliveryTag = msg.getMessageProperties().getDeliveryTag();
    channel.basicAck(deliveryTag, true);
    System.out.println("lazy receive " + new String(msg.getBody()));

  }

测试结果

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest
@RunWith(SpringRunner.class)
public class MQSenderTest {

  @Autowired
  private MQSender mqSender;

  @Test
  public void sendLazy() throws Exception {
    String msg = "hello spring boot";

    mqSender.sendLazy(msg + ":");
  }
}

果然在 6 秒后收到了消息 lazy receive hello spring boot:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • springcloud 熔断监控Hystrix Dashboard和Turbine

    springcloud 熔断监控Hystrix Dashboard和Turbine

    这篇文章主要介绍了springcloud 熔断监控Hystrix Dashboard和Turbine,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-08-08
  • Java编程一维数组转换成二维数组实例代码

    Java编程一维数组转换成二维数组实例代码

    这篇文章主要介绍了Java编程一维数组转换成二维数组,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-01-01
  • spring boot整合Shiro实现单点登录的示例代码

    spring boot整合Shiro实现单点登录的示例代码

    本篇文章主要介绍了spring boot整合Shiro实现单点登录的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-01-01
  • Java自定义异常_动力节点Java学院整理

    Java自定义异常_动力节点Java学院整理

    这篇文章主要介绍了Java自定义异常_动力节点Java学院整理的相关资料,需要的朋友可以参考下
    2017-04-04
  • springboot项目访问静态资源的配置代码实例

    springboot项目访问静态资源的配置代码实例

    这篇文章主要介绍了springboot项目访问静态资源的配置代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • java执行windows下cmd命令的方法

    java执行windows下cmd命令的方法

    这篇文章主要介绍了java执行windows下cmd命令的方法,较为详细的说明了Java执行Windows下CMD命令的方法,并总结了常用的CMD命令供大家参考,需要的朋友可以参考下
    2014-11-11
  • Java Web项目中编写定时任务的实现

    Java Web项目中编写定时任务的实现

    本篇文章主要介绍了Java Web项目中编写定时任务的实现,具有一定的参考价值,有兴趣的可以了解一下。
    2017-01-01
  • Java常见基本数据结构概览

    Java常见基本数据结构概览

    这篇文章主要介绍了Java常见的几种基本数据结构,包括其含义和用法,,需要的朋友可以参考下
    2017-09-09
  • jconsole使用介绍(图文)

    jconsole使用介绍(图文)

    大家在学习java的时候,难免会对jvm进行一些深入的了解。推荐大家使用jdk下面的jconsole.exe来辅助理解jvm的一些概念
    2015-12-12
  • JAVA版排序算法之快速排序示例

    JAVA版排序算法之快速排序示例

    这篇文章主要介绍了JAVA版排序算法之快速排序,结合实例形式分析了基于java版的遍历、递归实现快速排序功能的具体步骤与操作技巧,需要的朋友可以参考下
    2017-01-01

最新评论