文章目录
一、场景说明
在许多项目中,都需要用到延迟任务,例如
- 文章定时发布。
- 外卖订单超过15分钟未支付,自动取消。
- 客户预定自如房子后,24小时内未支付,房源自动释放 。
- 使用抢票软件订到车票后,1小时内未支付,自动取消。
二、解决方案
2.1 定时任务进行DB轮询
实现方式:通过一个线程定时的扫描数据库当天创建的订单,根据订单的创建时间来判断订单是否超 时,针对超时订单进行相关的更新操作。线程定时可采用SpringTask(单机)或XXL-Job(分布式)实现。
优点:实现简单
缺点:
- 系统订单数据量比较大,每隔1分钟轮询数据库,对服务器和数据库的内存消耗比较大。
- 存在延迟,即使1分钟扫描一次数据库,也会存在1分钟的延迟。
2.2 监听Redis过期key
实现方式:使用Redis的Keyspace Notifications,利用key失效的提供的回调机制,处理相关的业务实 现。 修改Redis配置文件打开redis.conf 文件,搜索 “notify-keyspace-events”,修改为 “notify- keyspace-events Ex”,至此Redis 就支持Key过期事件的监听。
@Component
public class RedisKeyExpirationListener implements MessageListener
{
private static final Logger logger =
LoggerFactory.getLogger(RedisKeyExpirationListener.class);
public static final String KEY_PREX = "test::order:queue";
@Override
public void onMessage(Message message, byte[] pattern)
{
try
{
String expiredKey = message.toString();
// 通过key来判断 if(!expiredKey.contains(KEY_PREX)) {
return; }
//满足条件处理具体的业务逻辑 }
catch (Exception e)
{
logger.error("失效事件失败",e); }
} }
优点: 基于Redis实现简单
缺点:
- 客户端断开后重连会导致所有事件丢失。
- 高并发场景下,存在大量的失效key场景会导致失效时间存在延迟。
- 此方案针对业务量较少且可靠性要求不高的场景使用。
不太可靠,可能百分之九十多会成功,但是可能会失败,这种方案适合对于执行任务的可靠度没有太高要求的,可以使用。比如一些通知类型的,下单之后通知付款,通知发送是否成功对于整个系统的业务流转并没有影响,用户没有收到通知也会自己去付款。但是取消订单这种就不适合,30分钟没有付款就需要取消,这种就可靠度就必须要高。
2.3 RabbitMQ死信队列+TTL过期消息
实现方式: 用户下单后,发布一条设置过期时间的消息。消息过期后会转移到死信队列。对死信队列进行消费监听,接收到消息后找到对应订单,如果订单还未付款就更新订单状态为已取消 。
eg:以订单取消为例。
生产者:下单方案-保存订单-发送一条消息到mq(订单id)
rabbitmq:exchange业务交换机接收到生产者的消息,把消息交给queue队列,(队列设置一个过期时间,方法为TTL)。在设定时间之后会过期然后交给DLX死信(过期的消息称为死信)交换机(绑定之后会自动提交),死信交换机再把消息转到死信队列。
消费者:监听死信队列,消费者拿到这个死信队列过期消息中存储的订单id。
优点: 支持高并发场景消息处理
缺点:
- 引入额外的消息队列,增加项目的维护和复杂度。
- 支持固定时长的消息延迟,针对任意时长的消息延迟需要进行扩展。
2.4 Redis SortedSet+MySQL延迟队列
实现方式:
- 利用Redis的SortedSet数据结构实现了文章定时发布任务的延迟队列。需要定时发布的文章,我们会写入一个发布任务记录到SortedSet中SortedSet的score中存储了文章的发布时间毫秒 值,默认会按照发布时间升序排序。另外,用XXL-Job分布式定时任务每秒从SortedSet查询小于或等于 当前时间毫秒值的记录,这些记录就是到期发布的文章任务,进而调用发布文章接口完成文章发布。
- 考虑到该方案过于依赖Redis持久化特性来实现任务持久化,我进一步优化了方案。在把定时任 务记录写入Redis的SortedSet同时,也备份到MySQL中,防止任务丢失。
- 这里其实还有一个问题,Redis的SortedSet如果存储数据过多,会导致SortedSet操作效率下 降。只有临近发布时间的任务数据才缓存在Redis中,其余任务先存储在MySQL。快到期的任务才使 用XXL-Job同步到Redis
Redis延迟微服务,把5分钟内要做的任务写入Redis,5分钟内以及5分钟以后的任务都需要写入Mysql,然后每隔5分钟Redis的任务被消费完之后,mysql补充5分钟内的新任务到Redis。(消费者全表扫描一定要扫描Redis中的任务消费)反复如此。
优点:高效可靠,适合任意时长的消息延迟 。(弥补了前三种方案的劣势)
缺点:需要额外维护一套MySQL的DB任务表。