延时队列应用于什么场景

延时队列顾名思义,即放置在该队列里面的消息是不需要立即消费的,而是等待一段时间之后取出消费。
那么,为什么需要延迟消费呢?我们来看以下的场景

网上商城下订单后30分钟后没有完成支付,取消订单(如:淘宝、去哪儿网)
    系统创建了预约之后,需要在预约时间到达前一小时提醒被预约的双方参会
    系统中的业务失败之后,需要重试

这些场景都非常常见,我们可以思考,比如第二个需求,系统创建了预约之后,需要在预约时间到达前一小时提醒被预约的双方参会。那么一天之中肯定是会有很多个预约的,时间也是不一定的,假设现在有1点 2点 3点 三个预约,如何让系统知道在当前时间等于0点 1点 2点给用户发送信息呢,是不是需要一个轮询,一直去查看所有的预约,比对当前的系统时间和预约提前一小时的时间是否相等呢?这样做非常浪费资源而且轮询的时间间隔不好控制。如果我们使用延时消息队列呢,我们在创建时把需要通知的预约放入消息中间件中,并且设置该消息的过期时间,等过期时间到达时再取出消费即可。
Rabbitmq实现延时队列一般而言有两种形式:
第一种方式:利用两个特性: Time To Live(TTL)、Dead Letter Exchanges(DLX)
第二种方式:利用rabbitmq中的插件x-delay-message

利用TTL DLX实现延时队列的方式

TTL DLX是什么

TTL
    RabbitMQ可以针对队列设置x-expires(则队列中所有的消息都有相同的过期时间)或者针对Message设置x-message-ttl(对消息进行单独设置,每条消息TTL可以不同),来控制消息的生存时间,如果超时(两者同时设置以最先到期的时间为准),则消息变为dead letter(死信)

Dead Letter Exchanges(DLX)
    RabbitMQ的Queue可以配置x-dead-letter-exchange和x-dead-letter-routing-key(可选)两个参数,如果队列内出现了dead letter,则按照这两个参数重新路由转发到指定的队列。
    x-dead-letter-exchange:出现dead letter之后将dead letter重新发送到指定exchange
    x-dead-letter-routing-key:出现dead letter之后将dead letter重新按照指定的routing-key发送

Springboot集成rabbitmq实现第一种方式

在pom.xml文件中增加rabbitmq的依赖

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

初始化queue exchange和queue及exchange之间的binding关系

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.amqp.core.Queue; import java.util.HashMap;
import java.util.Map; @Configuration
public class Config {
public static final String IMMEDIATE_QUEUE = "queue.demo.immediate";//立即消费的队列名称
public static final String IMMEDIATE_EXCHANGE = "exchange.demo.immediate";//立即消费的exchange
public static final String IMMEDIATE_ROUTING_KEY = "routingkey.demo.immediate";//立即消费的routing-key 名称
public static final String DELAY_QUEUE= "queue.demo.delay";//延时消费的队列名称
public static final String DEAD_LETTER_EXCHANGE = "exchange.demo.delay";//延时消费的exchange
public static final String DELAY_ROUTING_KEY = "routingkey.demo.delay";//延时消费的routing-key名称 // 创建一个立即消费队列
@Bean
public Queue immediateQueue() {
// 第一个参数是创建的queue的名字,第二个参数是是否支持持久化
return new Queue(IMMEDIATE_QUEUE, true);
} // 创建一个延时队列
@Bean
public Queue delayQueue() {
Map<String, Object> params = new HashMap<>();
// x-dead-letter-exchange 声明了队列里的死信转发到的DLX名称,
params.put("x-dead-letter-exchange", IMMEDIATE_EXCHANGE);
// x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称。
params.put("x-dead-letter-routing-key", IMMEDIATE_ROUTING_KEY);
return new Queue(DELAY_QUEUE, true, false, false, params);
} public DirectExchange immediateExchange() {
// 一共有三种构造方法,可以只传exchange的名字, 第二种,可以传exchange名字,是否支持持久化,是否可以自动删除,
//第三种在第二种参数上可以增加Map,Map中可以存放自定义exchange中的参数
return new DirectExchange(IMMEDIATE_EXCHANGE, true, false);
} @Bean public DirectExchange deadLetterExchange() {
// 一共有三种构造方法,可以只传exchange的名字, 第二种,可以传exchange名字,是否支持持久化,是否可以自动删除,
// 第三种在第二种参数上可以增加Map,Map中可以存放自定义exchange中的参数
return new DirectExchange(DEAD_LETTER_EXCHANGE, true, false);
} //把立即消费的队列和立即消费的exchange绑定在一起
@Bean
public Binding immediateBinding() {
return BindingBuilder.bind(immediateQueue()).to(immediateExchange()).with(IMMEDIATE_ROUTING_KEY);
} //把延时消费的队列和延时消费的exchange绑定在一起
@Bean
public Binding delayBinding() {
return BindingBuilder.bind(delayQueue()).to(deadLetterExchange()).with(DELAY_ROUTING_KEY);
}
}

生产者生产消息

import com.microservice.amqqp.amqp.config.Config;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import java.text.SimpleDateFormat;
import java.util.Date; /**
* 生产者生产消息
*/
@Component
public class ImmediateSender {
@Autowired
private RabbitTemplate rabbitTemplate; public void send(String msg, int delayTime) {
System.out.println("msg="+",delayTime" + delayTime);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
this.rabbitTemplate.convertAndSend(Config.DEAD_LETTER_EXCHANGE, Config.DELAY_ROUTING_KEY, msg, message -> {
message.getMessageProperties().setExpiration(delayTime + ""); System.out.println(sdf.format(new Date()) + " Delay sent."); return message;
});
} }

消费者消费消息:

import com.microservice.amqqp.amqp.config.Config;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component; import java.text.SimpleDateFormat;
import java.util.Date; /**
* 消费者消费消息
*/
@Component
@EnableRabbit
@Configuration
public class ImmediateReceiver {
@RabbitListener(queues = Config.IMMEDIATE_QUEUE)
@RabbitHandler
public void get(String msg) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("收到延时消息时间:"+sdf.format(new Date()) + " Delay sent.");
System.out.println("收到延时消息了:" + msg);
}
}

测试类:

import com.microservice.amqqp.amqp.send.ImmediateSender;
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; import java.util.concurrent.TimeUnit; @RunWith(SpringRunner.class)
@SpringBootTest
public class AmqpApplicationTests { @Autowired
ImmediateSender immediateSender;
@Test
public void test() {
immediateSender.send("我是一个延时消息",3000);//3秒 //让服务一直挂起,不然,接收消息时,服务已经停了
while(true){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} }
} }

第一次运行,需要进入rabbitmq管理界面,加上exchange,不然会报错(no exchange 'exchange.demo.immediate' in vhost '/')

添加方式:1.浏览器打开:http://127.0.0.1:15672  2.选择Exchanges 3.Add a new exchange  ,填写name:exchange.demo.immediate,type选择:direct,点击Add exchange ,完成。

运行测试类,结果:

再来一种测试:

import com.microservice.amqqp.amqp.send.ImmediateSender;
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;
import java.util.concurrent.TimeUnit; @RunWith(SpringRunner.class)
@SpringBootTest
public class AmqpApplicationTests1 { @Autowired
ImmediateSender immediateSender; /**
* 发送三条消息,设置延时时间,发现所有的都在等待;
* 这是因为符合先进先出原则,三条消息是依次被消费,并不会因为时间到了,就消费
*/
@Test
public void test() {
immediateSender.send("我是一个延时消息,睡10秒",10000);//10秒
immediateSender.send("我是一个延时消息,睡2秒",2000);//2秒
immediateSender.send("我是一个延时消息,睡1秒",1000);//1秒 //让服务一直挂起,不然,接收消息时,服务已经停了
while(true){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

结果:

经过测试,我们可以发现,当我们先增加一条过期时间大(10000)的A消息进入,之后再增加一个过期时间小的(1000)消息B,并没有出现想象中的B消息先被消费,A消息后被消费,而是出现了当10000过去的时候,AB消息同时被消费,也就是B消息的消费被阻塞了。

为什么会出现这样的现象呢?
我们知道利用TTL DLX特性实现的方式,实际上在第一个延时队列C里面设置了dlx,生产者生产了一条带ttl的消息放入了延时队列C中,等到延时时间到了,延时队列C中的消息变成了死信,根据延时队列C中设置的dlx的exchange的转发规则,转发到了实际消费队列D中,当该队列中的监听器监听到消息时就会正式开始消费。那么实际上延时队列中的消息也是放入队列中的,队列满足先进先出,而延时大的消息A还没出队,所以B消息也不能顺利出队。

上面实现方式的源码地址:https://github.com/qjm201000/micro_service_amqp_ttldlx.git

利用Rabbitmq的插件x-delay-message实现延时队列的方式

为了解决上面的问题,Rabbitmq实现了一个插件x-delay-message来实现延时队列。

安装插件:

1.rabbit官网下载插件
插件地址

2.找到这个插件

3.下载下来复制到D:\RabbitMQ Server\rabbitmq_server-3.7.8\plugins中

4.doc运行:rabbitmq-plugins enable rabbitmq_delayed_message_exchange

开始写代码:

配置:

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.CustomExchange;
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 XdelayConfig {
public static final String IMMEDIATE_QUEUE_XDELAY = "queue.xdelay.immediate";//立即消费的队列名称
public static final String DELAYED_EXCHANGE_XDELAY = "exchange.xdelay.delayed";//延时的exchange
public static final String DELAY_ROUTING_KEY_XDELAY = "routingkey.xdelay.delay";// // 创建一个立即消费队列
@Bean
public Queue immediateQueue() {
// 第一个参数是创建的queue的名字,第二个参数是是否支持持久化
return new Queue(IMMEDIATE_QUEUE_XDELAY, true);
} @Bean
public CustomExchange delayExchange() {
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-delayed-type", "direct");
return new CustomExchange(DELAYED_EXCHANGE_XDELAY, "x-delayed-message", true, false, args);
} //把立即消费的队列和延时消费的exchange绑定在一起
@Bean
public Binding bindingNotify() {
return BindingBuilder.bind(immediateQueue()).to(delayExchange()).with(DELAY_ROUTING_KEY_XDELAY).noargs();
}
}

发送:

import com.microservice.amqp.config.XdelayConfig;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import java.text.SimpleDateFormat;
import java.util.Date; @Component
public class XdelaySender { @Autowired
private RabbitTemplate rabbitTemplate; public void send(String msg, int delayTime) {
System.out.println("msg= "+msg+ ".delayTime" + delayTime);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
this.rabbitTemplate.convertAndSend(XdelayConfig.DELAYED_EXCHANGE_XDELAY, XdelayConfig.DELAY_ROUTING_KEY_XDELAY, msg, message -> {
message.getMessageProperties().setDelay(delayTime);
System.out.println(sdf.format(new Date()) + " Delay sent.");
return message;
});
}
}

接收:

import com.microservice.amqp.config.XdelayConfig;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component; import java.text.SimpleDateFormat;
import java.util.Date; @Component
@EnableRabbit
@Configuration
public class XdelayReceiver { @RabbitListener(queues = XdelayConfig.IMMEDIATE_QUEUE_XDELAY)
public void get(String msg) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("收到延时消息时间:"+sdf.format(new Date()) + " Delay sent.");
System.out.println("收到延时消息:" + msg);
}
}

test:

import com.microservice.amqp.send.XdelaySender;
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;
import java.util.concurrent.TimeUnit; @RunWith(SpringRunner.class)
@SpringBootTest
public class AmqpApplicationTests { @Autowired
XdelaySender xdelaySender; /**
* 发送三条消息,设置延时时间,谁时间到了,谁就消费
*/
@Test
public void test() {
xdelaySender.send("我来发一个测试消息,10秒", 10000);//10秒
xdelaySender.send("我来发一个测试消息,2秒", 2000);//2秒
xdelaySender.send("我来发一个测试消息,1秒", 2000);//1秒 //让服务一直挂起,不然,接收消息时,服务已经停了
while(true){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

结果:

源码地址:https://github.com/qjm201000/micro_service_amqp_xdelaymessage.git

微服务-springboot-rabbitmq:实现延时队列的更多相关文章

  1. RabbitMq 实现延时队列-Springboot版本

    rabbitmq本身没有实现延时队列,但是可以通过死信队列机制,自己实现延时队列: 原理:当队列中的消息超时成为死信后,会把消息死信重新发送到配置好的交换机中,然后分发到真实的消费队列: 步骤: 1. ...

  2. 基于rabbitMQ 消息延时队列方案 模拟电商超时未支付订单处理场景

    前言 传统处理超时订单 采取定时任务轮训数据库订单,并且批量处理.其弊端也是显而易见的:对服务器.数据库性会有很大的要求,并且当处理大量订单起来会很力不从心,而且实时性也不是特别好 当然传统的手法还可 ...

  3. rabbitMq实现延时队列

    原文:https://my.oschina.net/u/3266761/blog/1926588 rabbitMq是受欢迎的消息中间件之一,相比其他的消息中间件,具有高并发的特性(天生具备高并发高可用 ...

  4. 微服务springboot视频最新SpringBoot2.0.3版本技术视频教程【免费学习】

    超火爆的springboot微服务技术怎么学,看这里,springboot超详细的教程↓↓↓↓↓↓https://ke.qq.com/course/179440?tuin=9b386640 01.sp ...

  5. 【日常摘要】- RabbitMq实现延时队列

    简介 什么是延时队列? 一种带有延迟功能的消息队列 过程: 使用场景 比如存在某个业务场景 发起一个订单,但是处于未支付的状态?如何及时的关闭订单并退还库存? 如何定期检查处于退款订单是否已经成功退款 ...

  6. rabbitmq实现延时队列(死信队列)

    基于队列和基于消息的TTL TTL是time to live 的简称,顾名思义指的是消息的存活时间.rabbitMq可以从两种维度设置消息过期时间,分别是队列和消息本身. 队列消息过期时间-Per-Q ...

  7. Rabbitmq的延时队列的使用

    配置: spring: rabbitmq: addresses: connection-timeout: username: guest password: guest publisher-confi ...

  8. RabbitMQ及延时队列

    一.简介 我用过RabbirMQ的发布订阅模式,以及一对一的延迟队列. 1.RabbitMQ的有消息确认机制,消费一条则队列中少一条,也有对应的消费到消息及认为是消费成功这样的模式,一般使用前者. 发 ...

  9. 微服务-springboot+websocket在线聊天室

    一.引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId> ...

  10. 微服务-springboot日志配置

    springboot 默认会加载classpath:logback-spring.xml文件. springProfile 中的name名字对应application-xx.properties 中的 ...

随机推荐

  1. 网络库Asio交叉编译(Linux生成ARM)

    1.  Asio是一个跨平台的C++库,用于网络和底层I/O编程.Asio使用先进的C++方式提供了一系列的异步模型 2. 官方网址:http://think-async.com 3. 由于Asio库 ...

  2. WPF特效-粒子动画

    原文:WPF特效-粒子动画 WPF实现泡泡龙小游戏效果.     /// -Ball to Ball Collision - Detection and Handling    /// http:// ...

  3. 1-9 RHEL7-文件权限管理

    本节所讲内容: 文件的基本权限:r w x (UGO+ACL) 文件的高级权限:suid sgid sticky 第1章 文件的基本权限 1.1 权限的作用 通过对文件设定权限可以达到以下三种访问限制 ...

  4. 1 min 数据查询 SQL 优化

    问题 前几天线上数据库 IOPS 飙升,一直居高不下,最近并没有升级.遂查看数据库正在执行的 SQL 语句,发现有个查询离线设备的语句极其缓慢. 探寻原因 SELECT o.* FROM ( SELE ...

  5. Vue-cli入门(一)——项目搭建

    Vue-cli入门(一)——项目搭建 前言: Vue-cli是一款基于vue的项目脚手架工具,其集成了webpack环境和主要的依赖,对于我们的项目搭建.开发.打包.维护管理等都是非常的方便. 主要内 ...

  6. 【C++】小心使用文件读写模式:回车('\r') 换行('\n')问题的一次纠结经历

    原来没有仔细注意C++读写文件的二进制模式和文本模式,这次吃了大亏.(平台:windows  VS2012) BUG出现: 写了一个程序A,生成一个文本文件F保存在本地,然后用程序B读取此文件计算MD ...

  7. Python杂谈: __init__.py的作用

    我们经常在python的模块目录中会看到 "__init__.py"  这个文件,那么它到底有什么作用呢? 1. 标识该目录是一个python的模块包(module package ...

  8. 问题记录,Release模式和Debug运行效果不一样,Release必须加延时

    这个程序大体是这样一个逻辑,通过win32程序与设备交互,主线程先向设备发送命令要求 循环验证 然后一个线程专门负责接收设备返回信息 两边通过全局变量的变化来交流,主线程通过接收线程收到的信息设置界面 ...

  9. Windows 10 UWP 部署

      原文  http://youthlin.com/20151105.html 我们知道VS连接手机可以直接部署到手机里,但平板貌似无法这样干,平板与电脑连接没有丝毫反应……那么想看VS里写的uwp应 ...

  10. Editor.md v1.4.2 发布,改进自定义工具栏

    分享 <关于我> 分享  [中文纪录片]互联网时代                 http://pan.baidu.com/s/1qWkJfcS 分享 <HTML开发MacOSAp ...