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 基础就不介绍了,推荐看这个免费教程:

时间轮

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

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

| 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 过期监听实现定时任务。

近期热文推荐:

觉得不错,别忘了随手点赞+转发哦!

Original: https://www.cnblogs.com/javastack/p/16717958.html
Author: Java技术栈
Title: CTO 说了:谁再用 Redis 过期监听实现定时任务,立马滚蛋!

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/615571/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 数据结构基础知识

    数据结构 数据结构指数据的存储、组织方式。 数据结构 优点 缺点 栈 顶部元素插入和取出快 除顶部元素外,存取其他元素都很慢 队列 顶部元素取出和尾部元素插入快 存取其他元素都很慢…

    Java 2023年6月5日
    083
  • 尚医通项目总结(二)———-Mybatis与Mybatis Plus

    Mybatis是持久层解决方案,它是一个半自动化的ORM框架,底层封装了JDBC,可以简化对数据库的增删改查操作。Mybatis的半自动体现在,在查询关联对象或者关联集合对象时,需…

    Java 2023年6月5日
    096
  • Spring(十四):SpringAOP及AOP的三种实现方法

    一、什么是AOP AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP…

    Java 2023年6月15日
    049
  • Spring Cloud版本

    Spring Cloud版本演进情况如下: 版本名称 版本 Finchley snapshot版 Edgware snapshot版 Dalston SR1 当前最新稳定版本 Ca…

    Java 2023年5月30日
    076
  • C++实现双向RRT算法

    C++实现双向RRT算法 背景介绍 RRT(Rapidly-exploring Random Trees)是Steven M. LaValle和James J. Kuffner J…

    Java 2023年6月8日
    067
  • Spring Security过滤器链分析-初始化流程(8)

    过滤器链分析 提起Spring Security的实现原理,很多读者都会想到过滤器链。因为Spring Security中的所有功能都是通过过滤器来实现的,这些过滤器组成一个完整的…

    Java 2023年6月13日
    069
  • element-ui使用心得总结

    1、树形列表el-table的数据data中有hasChildren属性时,必须结合lazy,load,才能有点击展开的效果,缺其中一个要素没有配置,都不能点击展开; 2、树形列表…

    Java 2023年6月5日
    056
  • 常用命令

    tcpdump 获取80 端口 http请求内容 tcpdump ‘tcp port 80 and (((ip[2:2] – ((ip[0]&0xf)<>2))…

    Java 2023年6月16日
    053
  • springboot中使用mybatisplus自带插件实现分页

    springboot中使用mybatisplus自带插件实现分页 1.导入mybatisplus分页依赖 <dependency> <groupid>com…

    Java 2023年6月15日
    071
  • Spring:代理Filter:DelegatingFilterProxy原理和作用

    DelegatingFilterProxy就是一个对于servlet filter的代理,用这个类的好处主要是通过Spring容器来管理servlet filter的生命周期, 还…

    Java 2023年5月30日
    069
  • idea使用教程-模板的使用

    一、代码模板是什么 它的原理就是配置一些常用代码字母缩写,在输入简写时可以出现你预定义的固定模式的代码,使得开发效率大大提高,同时也可以增加个性化。最简单的例子就是在Java中输入…

    Java 2023年6月5日
    066
  • 【转载】vscode配置C/C++环境

    VScode中配置 C/C++ 环境 Tip:请在电脑端查看@零流@火星动力猿 2022.4.12 1. 下载编辑器VScode 官网:https://code.visualstu…

    Java 2023年6月9日
    084
  • 一级缓存与二级缓存

    1.一级缓存 一级缓存基于sqlSession默认开启,在操作数据库时需要构造SqlSession对象,在对象中有一个HashMap用于存储缓存数据。不同的SqlSession之间…

    Java 2023年6月13日
    079
  • maven工程servlet实例之导入项目依赖jar包

    maven工程servlet实例之导入项目依赖jar包 添加 jar 包的坐标时,还可以指定这个 jar 包将来的作用范围。每个 maven 工程都需要定义本工程的坐标,坐标是 m…

    Java 2023年6月6日
    074
  • java项目中VO、DTO以及Entity,各自是在什么情况下应用的

    按照标准来说: 举个例子: 在vo里,就应该有下面三个(因为对应html页面上三个字段) private string name; private string sex; priv…

    Java 2023年6月7日
    081
  • idea永久激活教程(新版)

    第一步 下载新版idea安装包 idea2022.x。下载方式(推荐):访问idea官网选择idea2022旗舰版本进行下载即可,不要选择community版本哦(communit…

    Java 2023年6月9日
    0114
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球