在各种订单管理环节,订单延时取消有很多种方式

1、定时扫描任务扫表springSchedule,xx-job等。每隔固定时间查询订单表,查询过期未支付的订单。批量更新为已关闭

优点:
	实现简单。
缺点:
	时间不精确,有间隔扫描时间误差,可能有延迟。
	数据库压力大,订单百万级别抗住不。
	如果是分布式部署,有重复消费风险,必须加上分布式锁
适用场景:
	日订单量万及以下,对时间监督要求不高的中小型系统。

2、JDK延迟队列DelayQueue 这是JAVA自带的延迟队列,基于优先级队列元素,实现DElayed指定接口延迟时间,队列按过期时间排序,过期才能取出来。订单创建时队列后台线程不断取出过期订单处理

优点
	不依赖外部组件时间相对精确

缺点
	内存风险巨大,可能会oom。
	数据会丢失,队列在JVM内存,服务重启数据丢失
	不支持分布式,无法集群共享
适用场景
	单机应用,适用数据量小,数据丢失不影响业务的系统,比如内部消息提醒。

3、Redis过期监听 Redis the key设置过期时间,过期时发送事件,监听这个事件处理业务。订单创建时,订单ID作为key存Redis,三十分钟过期,触发过期时间。

优点
	-
缺点
	Redis过期删除是时惰性删除+定期删除,key过期不会立刻删,延迟可能几秒几十秒,不精确。
	消息可能丢失,redis发布订阅不支持持久化
	会重复消费,多个消费者消费同一个过期事件
适用场景
	基本不推荐。

4、Redission分布式延迟队列。 Redission是Redis官方JAVA客户端,提供分布式延迟队列RDelayedQueue,底层基于Redis的Sorted Set,元素按过期时间排序,后台线程定时扫描,把过期元素移动到目标队列。

优点
	支持分布式,多个节点共享一个队列。
	数据持久化,数据不会丢
	时间比较精确,内部扫描间隔默认五秒,秒级延迟
	大量使用Lua脚本,保障原子性,不会有并发问题
缺点
	依赖Redis,且Redis需要高可用。
适用场景
	如果项目有使用redis,这种简单高效性价比高的方式强烈推荐项目使用

5、时间轮 时间轮是高效的延时任务调度算法,原理是把时间划分成格子,像钟表一样,指针不断转动,转到一个格子,就触发一格的任务,比如60个格子,每秒一格,任务延迟30秒就放到30号格子,指针转到30触发。,延迟超过一圈,用round轮次记录,指针转到时round减一,减到0任务才执行。

优点
	性能极添加和删除任务都是 O(1),时间复杂度,内存占用小。kafka/dobbo这些高性能框架都在使用时间轮处理延时任务。
缺点
	时间精度受限制于格子大小,格子一秒精度就是秒级。
	实现比较复杂,要处理Round任务,任务取消,指针跳跃等
适用场景
	高性能场景,时间精度秒级可以接受,或者延时时间比较短,几秒到几分钟场景

6、RocketMQ延迟消息

RocketMQ支持延迟消息,消息发送到broker后不会立刻投递,等延迟时间到了才投递,需要注意RocketMQ不支持任意时间延迟消息,只有18个固定级别,从一秒到两小时(1/5/10/30秒...)

优点
	完全解耦,订单服务和关单服务可以独立部署
	性能极高,单机十万QPS集群百万级、千万级别。
	消息可靠、持久化到磁盘,主从同步消息基本不丢失。
缺点
	延迟时间不灵活
	引入MQ增加系统复杂度,要处理消息丢失,重复消费顺序性等问题。
使用场景
	日订单百万、千万大型电商,对性能要求极高的平台,如淘宝/JD 

7/RabbitMQ 死信队列 RabbitMQ 不支持延迟消息,通过死信队列DLX模拟,给消息设置TTL过期时间,发送到没有消费的普通队列,消息过期后变成死信,自动路由到死信队列,消费死信队列处理任务

优点
	解耦,
	高性能
	高可用
	延迟时间精确到毫秒级别。
缺点
	对头阻塞,死信队列遵循先进先出,如果对头消息TTL是1小时,后面消息TTL 10分钟,后面消息必须等前面消息过期才能出队,会严重延迟,解决办法是使用RabbitMQ3.6x以后得延迟插件rabbitmq-delayed-message-exchange,基于消息级别的延迟,不会对头阻塞
	需要安装插件。
适用场景
	大并发场景,
	需要精确的延时时间

8、场景总结

日订单万级别以下  使用定时任务够用
单机应用数据可以丢 DelayQueue  如系统内部非核心提醒。
已经有redis  推荐redission
高性能内部组件  时间轮,如RPC框架,超时检测,连接池空闲检测等
日订单百万千万级别,必须MQ,rocketMQ或RabbitMQ