开场白:老铁们对于文章有错误、不准确,或需要补充的请留言讨论 ,大家共同学习。如果觉得还不错的请关注、留言、点赞 、收藏。 创作不易,且看且珍惜
在开发中,往往会遇到一些关于延时任务的需求。例如:生成订单 30 分钟未支付,则自动取消;生成订单 60 秒后,给用户发短信。
此处就涉及到 延时任务
和 定时任务
等问题。
这里只说 方案 思路 和 优缺点,代码不做实现。
序号 | 方案 | 思路 | 优点 | 缺点 |
---|---|---|---|---|
1 | quartz(定时任务 轮询) | 用quartz 定时去扫描订单 查询过期 | 简单 快捷 易行,业务小的可以考虑 支持集群操作 | (1)对服务器内存消耗大 (2)时效性差,存在延迟,比如你每隔 3 分钟扫描一次,那最坏的延迟时间就是 3 分钟 (3)效率低。假设你的订单有几千万条,每隔几分钟这样扫描一次,数据库损耗极大 (4)可能重复执行。如果第一次轮询耗时长,第二次轮询又开始了,会造成任务重复执行 (5)占用数据库资源,对数据库的压力比较大 |
2 | JDK 的延迟队列 (DelayQueue ) | 利用 JDK 自带的 DelayQueue 来实现,这是一个无界阻塞队列,该队列只有在延迟期满的时候才能从中获取元素,放入 DelayQueue 中的对象,是必须实现 Delayed 接口的。 | 效率高,任务触发时间延迟低。 | (1)服务器重启后,数据全部消失,怕宕机 (2)集群扩展相当麻烦 (3)因为内存条件限制的原因,比如下单未付款的订单数太多,那么很容易就出现 OOM 异常 (4)代码复杂度较高 |
3 | redis zset 缓存 | 利用 redis 的 zset,zset 是一个有序集合,每一个元素(member)都关联了一个 score,通过 score 排序来取集合中的值 | 重启或宕机 会丢失数据 (有人会说redis持久化 ,redis最大优点快 ,持久化会降低性能,就算持久化停机、宕机也会丢失一段时间的数据,具体不过多说。) | 存在一个致命的硬伤,在高并发条件下, 会出现多个线程消费同一个资源的情况 (解决方案: (1)用分布式锁,但是用分布式锁,性能下降了,该方案不细说。 (2)对 ZREM 的返回值进行判断,只有大于 0 的时候,才消费数据,于是将 consumerDelayMessage() 方法里的:) |
4 | redis键空间机制(Keyspace Notifications) | 利用该机制可以在 key 失效之后,提供一个回调,实际上是 redis 会给客户端发送一个消息。是需要 redis 版本 2.8 以上。 | (1)由于使用 Redis 作为消息通道,消息都存储在 Redis 中。如果发送程序或者任务处理程序挂了,重启之后,还有重新处理数据的可能性。 (2)做集群扩展相当方便 (3)时间准确度高 | (1)需要额外进行 redis 维护。 (2)Redis的发布/订阅目前是即发即弃(fire and forget)模式的,因此无法实现事件的可靠通知。也就是说,如果发布/订阅的客户端断链之后又重连,则在客户端断链期间的所有事件都丢失了。此方案不是太推荐,当然,如果你对可靠性要求不高,可以使用 |
5 | MQ 使用消息队列 | 我们可以采用 RabbitMQ 的延时队列。RabbitMQ 具有以下两个特性,可以实现延迟队列。 (1) RabbitMQ 可以针对 Queue 和 Message 设置 x-message-tt,来控制消息的生存时间,如果超时,则消息变为 dead letter 。(2) lRabbitMQ的 Queue 可以配置 x-dead-letter-exchange 和 x-dead-letter-routing-key(可选)两个参数,用来控制队列内出现了 deadletter,则按照这两个参数重新路由。结合以上两个特性,就可以模拟出延迟消息的功能。 | 高效,可以利用 rabbitmq 的分布式特性轻易的进行横向扩展,消息支持持久化增加了可靠性 | 本身的易用度要依赖于 rabbitMq 的运维。因为要引用 rabbitMq,所以复杂度和成本变高 |