CTO 说了:谁再用 Redis 过期监听实现定时任务,立马滚蛋!

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: CTO 说了:谁再用 Redis 过期监听实现定时任务,立马滚蛋!

前言

日前拜读阿牛老师的大作《领导:谁再用定时任务实现关闭订单,立马滚蛋!》发现其方案有若干瑕疵,特此抛砖引玉讨论一二。

https://juejin.cn/post/6987233263660040206

在电商、支付等领域,往往会有这样的场景,用户下单后放弃支付了,那这笔订单会在指定的时间段后进行关闭操作。

细心的你一定发现了像某宝、某东都有这样的逻辑,而且时间很准确,误差在 1s 内,那他们是怎么实现的呢?

一般实现的方法有几种:

  • 使用 RocketMQ、RabbitMQ、Pulsar 等消息队列的延时投递功能
  • 使用 Redisson 提供的 DelayedQueue

有一些方案虽然广为流传但存在着致命缺陷,不要用来实现延时任务:

  • 使用 Redis 的过期监听
  • 使用 RabbitMQ 的死信队列
  • 使用非持久化的时间轮

Redis 过期监听

在 Redis 官方手册的 keyspace-notifications: timing-of-expired-events 中明确指出:

Basically expired events are generated when the Redis server deletes the key and not when the time to live theoretically reaches the value of zero


Redis 自动过期的实现方式是:定时任务离线扫描并删除部分过期键;在访问键时惰性检查是否过期并删除过期键。


Redis 从未保证会在设定的过期时间立即删除并发送过期通知。实际上,过期通知晚于设定的过期时间数分钟的情况也比较常见。


此外键空间通知采用的是发送即忘(fire and forget)策略,并不像消息队列一样保证送达。当订阅事件的客户端会丢失所有在断线期间所有分发给它的事件。


这是一种比定时扫描数据库更 “LOW” 的解决方案,请不要使用。


有另一位大佬做了测试《请勿过度依赖Redis的过期监听》,有兴趣的朋友可以自行查阅。

https://juejin.cn/post/6844904158227595271

RabbitMQ 死信

死信(Dead Letter)是 RabbitMQ 提供的一种机制。

当一条消息满足下列条件之一那么它会成为死信:

  • 消息被否定确认(如 channel.basicNack)并且此时 requeue 属性被设置为 false。
  • 消息在队列的存活时间超过设置的 TTL 时间
  • 消息队列的消息数量已经超过最大队列长度

若配置了死信队列,死信会被 RabbitMQ 投到死信队

在 RabbitMQ 中创建死信队列的操作流程大概是:

  • 创建一个交换机作为死信交换机
  • 在业务队列中配置 x-dead-letter-exchange 和 x-dead-letter-routing-key,将第一步的交换机设为业务队列的死信交换机
  • 在死信交换机上创建队列,并监听此队列

死信队列的设计目的是为了存储没有被正常消费的消息,便于排查和重新投递。死信队列同样也没有对投递时间做出保证,在第一条消息成为死信之前,后面的消息即使过期也不会投递为死信。


为了解决这个问题,Rabbit 官方推出了延迟投递插件 rabbitmq-delayed-message-exchange ,推荐使用官方插件来做延时消息。


这里说点题外话,使用 Redis 过期监听或者 RabbitMQ 死信队列做延时任务都是以设计者预想之外的方式使用中间件,这种出其不意必自毙的行为通常会存在某些隐患,比如缺乏一致性和可靠性保证,吞吐量较低、资源泄漏等。


比较出名的一个事例是很多人使用 Redis 的 List 作为消息队列,以致于最后作者看不下去写了 Disque 并最后演变为 Redis Stream。工作中还是尽量不要滥用中间件,用专业的组件做专业的事。


Spring Boot 基础就不介绍了,推荐看这个免费教程:

https://github.com/javastacks/spring-boot-best-practice

时间轮

时间轮是一种很优秀的定时任务的数据结构,然而绝大多数时间轮实现是纯内存没有持久化的。

运行时间轮的进程崩溃之后其中所有的任务都会灰飞烟灭,所以奉劝各位勇士谨慎使用。

| Redisson DelayQueue

Redisson DelayQueue 是一种基于 Redis Zset 结构的延时队列实现。DelayQueue 中有一个名为 timeoutSetName 的有序集合,其中元素的 score 为投递时间戳。

DelayQueue 会定时使用 zrangebyscore 扫描已到投递时间的消息,然后把它们移动到就绪消息列表中。


DelayQueue 保证 Redis 不崩溃的情况下不会丢失消息,在没有更好的解决方案时不妨一试。


在数据库索引设计良好的情况下,定时扫描数据库中未完成的订单产生的开销并没有想象中那么大。


在使用 Redisson DelayQueue 等定时任务中间件时可以同时使用扫描数据库的方法作为补偿机制,避免中间件故障造成任务丢失。


结论

总结了几点如下:

  • 首先推荐使用 RocketMQ、Pulsar 等拥有定时投递功能的消息队列。
  • 在不方便获得专业消息队列时可以考虑使用 Redisson DelayQueue 等基于 Redis 的延时队列方案,但要为 Redis 崩溃等情况设计补偿保护机制。
  • 在无法使用 Redisson DelayQueue 等方案时可以考虑使用时间轮。由于时间轮重启远比 Redis 重启要频繁,定时扫库等保护机制更为重要。
  • 永远不要使用 Redis 过期监听实现定时任务。


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1月前
|
存储 NoSQL Redis
04- Redis的数据过期策略有哪些 ?
Redis的数据过期策略包括**惰性删除**和**定期删除**。惰性删除在取出key时检查是否过期,节省CPU但可能延迟清理。定期删除则每隔一定时间删除一批过期key,通过限制操作频率减少CPU影响。默认每秒扫描10次,随机抽取20个键,若25%已过期则继续检查,最大执行时间25ms。Redis使用这两种策略的结合以平衡内存和CPU使用。
17 1
|
1月前
|
NoSQL Redis 数据库
Redis的过期键是如何处理的?
【4月更文挑战第3天】Redis过期键处理采用被动删除和主动删除策略。被动删除在客户端访问时检查并删除过期键,节省CPU但可能导致键占用内存。主动删除包括定时和周期性删除,确保即使无客户端访问也能及时清理,但可能增加CPU开销。实际操作中,两者结合使用以平衡内存和CPU资源。
13 1
|
14天前
|
存储 NoSQL Redis
【Redis】Redis如何实现key的过期删除
【Redis】Redis如何实现key的过期删除
|
3天前
|
存储 NoSQL Java
熟悉Redis吗,那Redis的过期键删除策略是什么
对于Redis,我们业务开发一般都只关心Redis键值对的查询、修改操作,可能因为懒或者只想能用就行,呵呵。很少关心键值对存储在什么地方、键值对过期了会怎么样、Redis有没什么策略处理过期的键、Redis处理过期键又有什么作用?但这些问题却是Java程序员在Redis上进阶的必备知识,不要埋怨Java要学习的系统知识为什么这么多,因为这些确确实实是进阶的程序员所必须掌握的。我们往下看看~
25 2
熟悉Redis吗,那Redis的过期键删除策略是什么
|
15天前
|
缓存 NoSQL 算法
【Redis系列笔记】内存淘汰及过期删除
Redis是一个内存键值对数据库,所以对于内存的管理尤为重要。Redis内部对于内存的管理主要包含两个方向,过期删除策略和数据淘汰策略。内存淘汰策略指在Redis内存使用达到一定阈值的时候,执行某种策略释放内存空间,以便于接收新的数据。数据过期删除策略是指在数据的有效时间到期后,如何从内存中删除这些数据的规则。
34 0
|
16天前
|
存储 缓存 NoSQL
Redis入门到通关之Redis内存淘汰(内存过期)策略
Redis入门到通关之Redis内存淘汰(内存过期)策略
31 3
|
1月前
|
存储 NoSQL 算法
09- Redis分片集群中数据是怎么存储和读取的 ?
Redis分片集群使用哈希槽分区算法,包含16384个槽(0-16383)。数据存储时,通过CRC16算法对key计算并模16383,确定槽位,进而分配至对应节点。读取时,根据槽位找到相应节点直接操作。
66 12
|
1天前
|
存储 监控 NoSQL
Redis哨兵&分片集群
Redis哨兵&分片集群
6 0
|
2天前
|
NoSQL 算法 Java
深入浅出Redis(八):Redis的集群模式
深入浅出Redis(八):Redis的集群模式