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

在电商、支付等领域,往往会有这样的场景,用户下单后放弃支付了,那这笔订单会在指定的时间段后进行关闭操作,细心的你一定发现了像某宝、某东都有这样的逻辑,而且时间很准确,误差在1s内;那他们是怎么实现的呢?

一般实现的方法有几种:

  1. 使用 rocketmq、rabbitmq、pulsar 等消息队列的延时投递功能
  2. 使用 redisson 提供的 DelayedQueue

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

  1. 使用 redis 的过期监听
  2. 使用 rabbitmq 的死信队列
  3. 使用非持久化的时间轮

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

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

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

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。工作中还是尽量不要滥用中间件,用专业的组件做专业的事

时间轮

时间轮是一种很优秀的定时任务的数据结构,然而绝大多数时间轮实现是纯内存没有持久化的。运行时间轮的进程崩溃之后其中所有的任务都会灰飞烟灭,所以奉劝各位勇士谨慎使用。

redisson delayqueue

redisson delayqueue 是一种基于 redis zset 结构的延时队列实现。delayqueue 中有一个名为 timeoutSetName 的有序集合,其中元素的 score 为投递时间戳。delayqueue 会定时使用 zrangebyscore 扫描已到投递时间的消息,然后把它们移动到就绪消息列表中。

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

在数据库索引设计良好的情况下,定时扫描数据库中未完成的订单产生的开销并没有想象中那么大。在使用 redisson delayqueue 等定时任务中间件时可以同时使用扫描数据库的方法作为补偿机制,避免中间件故障造成任务丢失。

结论

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

领导:谁再用redis过期监听实现关闭订单,立马滚蛋!的更多相关文章

  1. redis事件监听及在订单系统中的使用

    https://blog.csdn.net/qq_37334135/article/details/77717248 通常在网上买好物品,或者说手机扫码后,点击付款,这时就会向后台发送请求,生成订单信 ...

  2. springboot使用Redis,监听Redis键过期的事件设置与使用代码

    我使用的是Windows下的Redis服务,所以一下Redis设置都是在Windows平台进行. 1.修改Redis配置文件 1.1:Windows下的Redis存在两个配置文件 修改带有servic ...

  3. 请勿过度依赖Redis的过期监听!!

    作者:迪壳 https://juejin.im/post/6844904158227595271 Redis 过期监听场景 业务中有类似等待一定时间之后执行某种行为的需求 , 比如 30 分钟之后关闭 ...

  4. spring boot 实现redis 的key的过期监听,执行自己的业务

    最近几天进一步了解了一下redis,发现了key的过期监听功能,实现方式如下: 在redis的配置文件 redis.conf 中找到"EVENT NOTIFICATION"模块, ...

  5. SpringBoot监听redis订阅监听和发布订阅

    前言 我们可以在redis中发布一条订阅到通道中,所有监听了这个通道的都可以收到这个发布的内容! redis订阅监听配置类 代码如下: RedisListenerConfig.java package ...

  6. Java面板Panel的使用,监听窗口关闭事件

    面板Panel的使用 待解决问题: 1.设计模式:适配器模式 2.frame.setLayout(null); package GUI; import javax.swing.*; import ja ...

  7. 监听JVM关闭

    使用Runtime的addShutdownHook(thread)方法: for(int i=0; i<5; i++){ System.out.println(i); } Thread th = ...

  8. JS监听页面关闭

    JS可以监听浏览器页面的关闭,主要使用了window对象的onbeforeunload方法 在以前(旧版本的浏览器中),可以自定义提示文案 window.onbeforeunload = functi ...

  9. Android 如何监听输入法关闭事件

    假设有如下界面(输入法的上面的输入区域是用Dialog实现的) 要求当输入法关闭的时候,Dialog也一起关闭,这样用户就不需要返回两次了. 网上找了很多资料都没有很好的解决这个问题,输入法是第三方程 ...

随机推荐

  1. Hash冲突以及解决

    哈希函数:它把一个大范围的数字哈希(转化)成一个小范围的数字,这个小范围的数对应着数组的下标.使用哈希函数向数组插入数据后,这个数组就是哈希表. 冲突 当冲突产生时,一个方法是通过系统的方法找到数组的 ...

  2. linux find命令 -mtime参数 根据修改时间查找文件

    命令:find 搜索路径 -mtime n 主要说明n的含义: 例: n=5 "5"指的是前 5~6 天那一天修改的文件 n=-5 "-5"指的是 5 天内修改 ...

  3. 查域名对应ip,测试端口是否可访问通

    根据命令查询软件包名称 yum provides */nslookup 根据域名解析ip nslookup 域名 示例:nslookup smtp.163.com 测试端口 telnet ip 端口 ...

  4. 从电路到UI系列之一——常见显示控制接口的认识

    引入 我之前有两方面的使用屏幕的案例.在做的模块上需要用到屏幕来显示的时候,全是SPI屏幕,并口屏幕和IIC屏幕.在研究了驱动(或者是绘图函数更加合适)之后,我发现其实他们就是直接写显存,这个显存在面 ...

  5. ADO访问Excel

    需要安装驱动:Microsoft Access Database Engine,可搜索下载,有64位和32位之分. 随便新建一个后缀名为udl的文件,双击打开.注意,现如今一般都是64位系统,双击打开 ...

  6. C语言基础部分练习(http://acm.hgnu.edu.cn)

    前言 最近有朋友和同学找我要c语言基础练习答案,为了方便分享,放在我的博客上了,如果对你确实有帮助,可以考虑点下赞或打赏哦(都能通过,没有专注于搞算法,所以有的地方可以优化,欢迎在评论区留言) A. ...

  7. javaWeb代码整理02-jdbcTemplete数据库连接工具

    jar包: maven坐标: /**属于spring框架的包*/<dependency> <groupId>org.springframework</groupId> ...

  8. 对比学习 ——simsiam 代码解析。

    ​  目录 1 : 事先准备 . 2 : 代码阅读. 2.1: 数据读取 2.2: 模型载入 3 训练过程: 4 测试过程: 5 :线性验证 6 : 用自己数据集进行对比学习. 第一:  改数据集 : ...

  9. docker入门_image、container相关命令

    docker入门_image.container相关命令 镜像仓库服务.镜像仓库.镜像相关概念 镜像仓库服务:docker镜像仓库服务.阿里云镜像服务 镜像仓库:docker镜像仓库服务中会有很多仓库 ...

  10. Hadoop(一)Hadoop核心架构与安装

    Hadoop是什么 大白话,Hadoop是个存储数据,计算数据的分布式框架.核心组件是HDFS.MapReduce.Yarn. HDFS:分布式存储 MapReduce:分布式计算 Yarn:调度Ma ...