一、基本概念
什么是延时队列?首先它要具有队列的特性,再给它附加一个延迟消费队列消息的功能,延迟队列相对比普通队列,区别就在延时的特性上,普通队列先进先出,按入队顺序进行处理,而延时队列中的元素在入队时会指定一个延时时间,希望能够在指定时间到了以后处理。

二、使用场景
业务类场景:
1、支付订单成功后,指定时间以后将支付结果通知给接入方。
2、淘宝订单业务:下单之后如果三十分钟之内没有付款就自动取消订单。
3、美团或饿了吧订餐通知:下单成功后60s之后给用户发送短信通知。
4、会议预定系统,在预定会议开始前半小时通知所有预定该会议的用户。
5、IT问题工单超过24小时未处理,则自动拉企微群提醒相关责任人。
6、用户下单外卖以后,距离超时时间还有10分钟时提醒外卖小哥即将超时。

技术框架场景∶
1、关闭空闲连接。服务器中,有很多客户端的连接,空闲一段时间之后需要关闭之。
2、缓存。缓存中的对象,超过了空闲时间,需要从缓存中移出。
3、任务超时处理。在网络协议滑动窗口请求应答式交互时,处理超时未响应的请求等。
上面的这些场景都可以通过延时队列解决。

三、实现方案
1、定时任务轮询数据库
2、Java提供的DelayQueue
3、netty提供的时间轮算法实现类HashedWheelTimer
4、消息中间件(rabbitmq、rocketmq.kafka)延迟消息
5、redis的sorted set或redis key过期回调

四、实现落地
1)、定时任务轮询数据库
采用定时任务实现延迟,对业务表进行轮询判断,订单到点执行,有一点点误差。实现如下:1)使用单机版的spring sceduled+分布式锁,代码如下:

@Slf4j
@Configuration
@EnableScheduling
public class OrderAutoCancelSpringJob {
@Resource
private DistributeLockHelper distributeLockHelper;
private static final String ORDER_AUTO_CANCEL_LOCK = "orderAutoCancelLocScheduled(cron = "0/60 * * ** ?")
public void cancelOrder() {
	if(!distributeLockHelper.tryLock(ORDER_AUTO_CANCEL_LOCKTimeUnit.MINUTES1)){
		return;
	try{
		//执行业务逻辑
	}finally{
		distributeLockHelper.unlock(ORDER_AUTO_CANCEL_LOCK);

2)使用分布式调度框架,常见的有xxl-job、Elastic-Job、Quartz、Saturn,配置频率为每几秒一次的定时任务,如果处理的数据量比较大,可以利用分布式调度框架的分片功能并行处理,大大提升数据处理的能力,加快处理速度。

3)优缺点
优点:
实现简单,不用引入任何中间件,各个业务模块可以自行定义延迟执行规则。
缺点∶
完全由业务代码控制,重复代码多,不论是否有待执行的数据,都要空轮询且需要频繁访问数据库,另外由于是定时轮询的,会有一点点误差。

  1. 适用场景
    该方案在互联网应用还是比较广泛的,适合定实时性要求没那么精确,允许有一点误差的场景。

5)使用示例及代码演示
以订单为例,超过指定时间后,订单自动取消
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

基于XXL-JOB:
在这里插入图片描述

2、DelayQueue延迟队列

1)什么是DelayQueue?
DelayQueve是一个无界的BlockingQueve,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期时间最长。注意︰不能将null元素放置到这种队列中。

2)实现注意事项
队列元素需要实现Delayed接口,getDelay方法用于设置延迟时间,compareTo方法用于对队列中的元素进行排序。入队:
put()、offer(),线程安全。
出队:
poll(),非阻塞方式获取,没有到期的元素直接返回null。
take(),阻塞方式获取,没有到期的元素线程将会等待。

3)优缺点
优点:
JDK自带的,不用引入其他框架或中间件,使用简单方便。
缺点∶
不支持分布式或持久化,重启后会丢失,如果订单并发量非常大,因为DelayQueve是无界的,订单量越大,队列内的对象就越多,可能造成0OM的风险。所以使用DelayQueue实现延时任务,只适用于任务量较小的情况。

4)适用场景
适用于不需要持久化任务量少的单机任务,能容忍丢失。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3、时间轮算法延迟队列

1)时间轮算法是什么及实现原理?
时间轮算法,参考的时钟,从时钟中得到的一些启发设计出来的,简单的讲它是一个存储定时任务的环形队列,底层采用数组实现,数组中的每个元素可以存放一个延时任务列表。延时任务列表是一个双向链表,链表中的每一项表示的都是一个延时任务。

2)常见实现方案
以netty的HashedWheelTimer为例,算法规则如下:假设共有八个槽0~7,槽的时间单位为1秒
现在要加入一个延时5秒的任务,计算方式就是5%8+ 1 = 6,即放在槽位为6,下标为5的那个槽中。
假如要加入一个延时50s的任务,计算方式就是50% 8+1= 3,即应该放在槽位是3,下标是2的位置。但是记住这里有轮次的概念,轮次=(50 - 1)/8 = 6,即轮数记为6。也就是说当循环6轮之后扫到下标的2的这个槽位会触发这个任务。

对于延迟超过时间轮所能表示的范围有两种处理方式:
一是通过增加一个字段-轮数,Netty就是这样实现的。
二是多层次时间轮,Kafka是这样实现的,跟我们的手表非常像,像我们秒针走一圈,分针走一格,分针走一圈,时针走一格。相比而言Netty的实现会有空推进的问题,而Kafka采用DelayQueve以槽为单位,利用空间换时间的思想解决了空推进的问题。它们的任务插入和删除时间复杂度都为0(1)

3)优缺点
优点:只需要一个线程推进时间轮,查询效率高,性能相对好一些。
缺点:内存占用高,任务有较大的耗时会影响时间轮的正确性,不支持分布式运行和持久化运行。)适用场景
适用于对时效性不高的,可快速执行的小任务,能够做到高性能,低消耗,应用的场景:心跳检测(客户端探活)
会话、请求是否超时消息延迟推送

5)使用示例及代码演示

public final static Timer HASHED_WHEEL_TINER = new HashedWheelTimer(new DefaultThreadFactory(" nettylelaylsg"),18L,TimeUnit.MILLISECONDS,512,true);

在这里插入图片描述
在这里插入图片描述
4、消息中间件延时队列

1)常见的实现方案. rocketmq
rocketmq先把消息按照延时时间段发到指定的队列中,然后通过一个定时器轮询这些队列,查看消息是否到期,如果到期就把这个消息发到指定的topic队列,流程图如下所示︰基于rocketmq实现延时队列原理

注意点:
RocketMQ延时消息的延迟时长不支持随意时长的延迟,是通过特定的延迟等级来指定的。默认支持18个等级的延迟消息,延时等级定义在RocketMQ服务端的MessageStoreConfig类中的如下变量中:

private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m ?m 8m 9m 10m 28m 30m 1h 2h";

发消息时,设置delayLevel等级即可:msg.setDelayLevel(level)。level有以下三种情况:
level == 0,消息为非延迟消息
1<=level<=maxLevel,消息延迟特定时间,
例如level1,延迟1slevel > maxLevel,则level maxLevel
例如level==20,延迟2h注意∶如果希望支持超过2h的延时消息,需要修改时间配置和对应的level

kafkal
不支持延时消息的能力,实现方案可参考rocketmq

2)优缺点
优点∶基于消息中间件可以快速实现延时队列,而且天然支持消息消费的有序性、消息持久化、ACK机制等。缺点∶没有接入上面中间件的团队,需要额外引入、增加了部署和运维成本。

3)适用场景
一般大厂的方案,适用于大量延时任务的场景。

5、基于redis延时队列

1)常见的实现方案
.基于redis sortedset实现.
基于redisson提供的api实现.

基于redis过期回调实现
将延时任务添加到redis,通过监听redis过期回调事件,对到期的任务进行处理。

2)优缺点
优点:基于redis完成了任务持久化,且支持分布式运行。
缺点∶缺乏队列顺序消息特性,相同score的任务无法顺序执行,缺乏ack特性,若任务执行失败,而队列的任务被删除了,就丢失了。

3)适用场景
适用于对顺序要求不高、允许任务丢失的场景,比如延时通知场景

首先它要具有队列的特性,再给它附加一个延迟消费队列消息的功能,延迟队列相对比普通队列,区别就在延时的特性上,普通队列先进先出,按入队顺序进行处理,而延时队列中的元素在入队时会指定一个延时时间,希望能够在指定时间到了以后处理。时间轮算法,参考的时钟,从时钟中得到的一些启发设计出来的,简单的讲它是一个存储定时任务的环形队列,底层采用数组实现,数组中的每个元素可以存放一个延时任务列表。缺点∶缺乏队列顺序消息特性,相同score的任务无法顺序执行,缺乏ack特性,若任务执行失败,而队列的任务被删除了,就丢失了。 一个简单的sqs消息轮询器。 它能做什么 ? 它提供了在较短的时间间隔后调用AWS :: SQS上的receiveMessage()并将其放入内部消息列表并逐一发出的功能,当内部消息列表为空时,它将再次从SQS中提取消息,依此类推。 想法是仅在我们处理完上一条消息后才提取下一条消息。 这可以帮助扩展应用程序中使用相同消息队列的某些区域。 - message var Poller = require('aws-sqs-poller'), options = { name : "your-queue", // required useIAMRole : true // optional
延迟队列的实现方案 一、应用场景 什么是延时队列?顾名思义:首先它要具有队列的特性,再给它附加一个延迟消费队列消息的功能,也就是说可以指定队列中的消息在哪个时间点被消费。 延时队列在项目中的应用场景是比较多的,尤其像电商类平台: 1、订单成功后,在30分钟内没有支付,自动取消订单 2、外卖平台发送订餐通知,下单成功后60s给用户推送短信。 3、如果订单一直处于某一个未完结状态时,及时处理关单,并退还库存 4、淘宝新建商户一个月内还没上传商品信息,将冻结商铺等 5、公司的会议预定系统,在会议预定成功后,会在会
        早期需要延迟处理的业务场景,更多的是通过定时任务扫表,然后执行满足条件的记录,具有频率高、命中低、资源消耗大的缺点。随着消息中间件的普及,延迟消息可以很好的处理这种场景,本文主要介绍延迟消息的使用场景以及基于常见的消息中间件如何实现延迟队列,最后给出了一个在网易公开课使用延迟队列的实践。 一、使用场景 消息队列中间件(简称消息中间件)是指利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型,它可以在分布式环境下提供应用解耦、弹性伸缩、冗余存储、流量削峰、异步通信、数据同步等等功能,其作为分布式系统架构中的一个重要组件,有着举足轻重的地位。 目前开源的消息中间件可谓...
【C#】 定时器设计过程的记录 在Windows 系统上 C# 常用定时器有三种,而最高进度的定时器误差 58ms 则个误差数字无法应用 16ms 级别的定时轮询,但还有另外的高精度定时器,这不是还有其他方法吗,对有的,空转也指的是直接让线程一只空转,设置休眠线程的间隔来达到同样的目的,但这样就无法在休眠的时间内执行其他事情,弊端还很多; 另外的方式,也有框架实现的,误差虽然有很高的降低,但现在...
DelayQueue简介 1.DelayQueue是一个无界阻塞队列队列内部使用PriorityQueue来实现。要添加进去的元素必须实现Delayed接口的类对象,在创建元素时可以指定多久才能从队列中获取当前元素,只有在延迟期满时才能从中提取元素; 2.该队列头部是延迟期满后保存时间最长的Delayed元素; 3.如果延迟都没有期满,则队列没有头部,并且poll将返回null; 4.当...
延迟队列,顾名思义它是一种带有延迟功能的消息队列。 那么,是在什么场景下我才需要这样的队列呢? 很多时候我们会有延时处理一个任务的需求,比如说: 2个小时后给用户发送短信。 15分钟后关闭网络连接。 2分钟后再次尝试回调。 下面我们来分别探讨一下几种实现方案: Java中的DelayQueue Java中的DelayQueue位于java.util.concurrent包
延时队列的几种实现方式 何为延迟队列? 顾名思义,首先它要具有队列的特性,再给它附加一个延迟消费队列消息的功能,也就是说可以指定队列中的消息在哪个时间点被消费。 延时队列能做什么? 延时队列多用于需要延时工作的场景。最常见的是以下场景: 延迟消费,比如: 1 ,订单成功后,在 30 分钟内没有支付,自动取消订单 2 ,如果订单一直处于某一个未完结状态时,及时处理关单,并退还库存 3 ,支付成功后, 2 秒后查询支付结果 4 , …… 如何实现? 实现延时队列的方式有很多种,本文主要介绍以下几种常见的方式:
最近在项目里做了非常简单的审批流程,根据领导要求 需要定时检查 登录用户是否 有表单需要去审批 ,一开始以为用spring  task定时任务调度可能更易于实现,一上手发现,task定时后台任务,比如清理缓存,清理日志等更为方便好用,而前台页面的定时任务,用js的定时器setInterval更加方便,使用起来比较简单。贴出项目中的代码:/*审批 轮询*/ var task = setInterv...
[b]简介:[/b] 综观目前的 Web 应用,多数应用都具备任务调度的功能。本文由浅入深介绍了几种任务调度的 Java 实现方法,包括 Timer,Scheduler, Quartz 以及 JCron Tab,并对其优缺点进行比较,目的在于给需要开发任务调度的程序员提供有价值的参考。 任务调度是指基于给定时间点,给定时间间隔或者给定执行次数自动执行任务。本文由浅入深介绍四种任务...
Modbus是一种常见的数据通信协议,被广泛应用于工业自动化、能源管理等领域。Modbus定时轮询是指在一定时间间隔内,对Modbus设备进行周期性的请求数据操作,以实现数据采集和控制等功能。 在Modbus定时轮询实例中,首先需要确定轮询时间间隔,一般根据实际情况来确定。例如,对于实时性要求比较高的控制系统,可以设置较短的时间间隔,如10ms或更短;对于数据采集等非实时性要求比较高的系统,可以设置较长的时间间隔,如1s或更长。 其次,需要确定轮询的寄存器地址和数据类型,包括读写操作、寄存器类型、寄存器地址和数据格式等。例如,读取位操作可以使用读线圈或读离散输入寄存器;读取模拟量数据可以使用读保持寄存器或读输入寄存器。 最后,需要编写Modbus定时轮询程序,实现对Modbus设备的定时数据操作。程序可以使用各种编程语言实现,例如C语言、Python等。在程序中,可以使用Modbus协议库进行数据采集和控制操作,也可以使用现成的Modbus通讯软件进行调试和测试。 总之,Modbus定时轮询是实现工业自动化和能源管理等领域数据采集和控制的关键技术之一。在实际应用中,需要根据实际情况合理设置轮询时间间隔、寄存器地址和数据类型,并编写稳定可靠的程序实现数据采集和控制操作。