领导:谁再用redis过期监听实现关闭订单,立马滚蛋!
日前拜读阿牛老师的大作 领导:谁再用定时任务实现关闭订单,立马滚蛋! 发现其方案有若干瑕疵,特此抛砖引玉讨论一二。
在电商、支付等领域,往往会有这样的场景,用户下单后放弃支付了,那这笔订单会在指定的时间段后进行关闭操作,细心的你一定发现了像某宝、某东都有这样的逻辑,而且时间很准确,误差在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 从未保证会在设定的过期时间立即删除并发送过期通知。实际上,过期通知晚于设定的过期时间数分钟的情况也比较常见。
这是一种比定时扫描数据库更 “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 等定时任务中间件时可以同时使用扫描数据库的方法作为补偿机制,避免中间件故障造成任务丢失。
结论
- 首先推荐使用 rocketmq、pulsar 等拥有定时投递功能的消息队列。
- 在不方便获得专业消息队列时可以考虑使用 redisson delayqueue 等基于 redis 的延时队列方案,但要为 redis 崩溃等情况设计补偿保护机制。
- 在无法使用 redisson delayqueue 等方案时可以考虑使用时间轮。由于时间轮重启远比 redis 重启要频繁,定时扫库等保护机制更为重要。
- 永远不要使用 redis 过期监听实现定时任务。
领导:谁再用redis过期监听实现关闭订单,立马滚蛋!的更多相关文章
- redis事件监听及在订单系统中的使用
https://blog.csdn.net/qq_37334135/article/details/77717248 通常在网上买好物品,或者说手机扫码后,点击付款,这时就会向后台发送请求,生成订单信 ...
- springboot使用Redis,监听Redis键过期的事件设置与使用代码
我使用的是Windows下的Redis服务,所以一下Redis设置都是在Windows平台进行. 1.修改Redis配置文件 1.1:Windows下的Redis存在两个配置文件 修改带有servic ...
- 请勿过度依赖Redis的过期监听!!
作者:迪壳 https://juejin.im/post/6844904158227595271 Redis 过期监听场景 业务中有类似等待一定时间之后执行某种行为的需求 , 比如 30 分钟之后关闭 ...
- spring boot 实现redis 的key的过期监听,执行自己的业务
最近几天进一步了解了一下redis,发现了key的过期监听功能,实现方式如下: 在redis的配置文件 redis.conf 中找到"EVENT NOTIFICATION"模块, ...
- SpringBoot监听redis订阅监听和发布订阅
前言 我们可以在redis中发布一条订阅到通道中,所有监听了这个通道的都可以收到这个发布的内容! redis订阅监听配置类 代码如下: RedisListenerConfig.java package ...
- Java面板Panel的使用,监听窗口关闭事件
面板Panel的使用 待解决问题: 1.设计模式:适配器模式 2.frame.setLayout(null); package GUI; import javax.swing.*; import ja ...
- 监听JVM关闭
使用Runtime的addShutdownHook(thread)方法: for(int i=0; i<5; i++){ System.out.println(i); } Thread th = ...
- JS监听页面关闭
JS可以监听浏览器页面的关闭,主要使用了window对象的onbeforeunload方法 在以前(旧版本的浏览器中),可以自定义提示文案 window.onbeforeunload = functi ...
- Android 如何监听输入法关闭事件
假设有如下界面(输入法的上面的输入区域是用Dialog实现的) 要求当输入法关闭的时候,Dialog也一起关闭,这样用户就不需要返回两次了. 网上找了很多资料都没有很好的解决这个问题,输入法是第三方程 ...
随机推荐
- Spring Boot-Profile
文章目录 前言 一.Profile是什么? 二.使用步骤 1.多Profile文件 2.使用yml方式 3.激活方式 总结 前言 不同的环境解释:比如我们开发人员使用开发环境,项目发布时使用生产环境, ...
- 32位x86处理器架构
我们看看32 位 x86 处理器的基本架构特点.这些处理器包括了 Intel IA-32 系列中的成员和所有 32 位 AMD 处理器. 操作模式 x86 处理器有三个主要的操作模式:保护模式.实地址 ...
- java高级用法之:在JNA中使用类型映射
目录 简介 类型映射的本质 TypeMapper NativeMapped 总结 简介 JNA中有很多种映射,library的映射,函数的映射还有函数参数和返回值的映射,libary和函数的映射比较简 ...
- Java枚举类与常用方法
小简博客 - 小简的技术栈,专注Java及其他计算机技术.互联网技术教程 (ideaopen.cn) 枚举类 如何创建 首先,从名字就可以看出,枚举是一个类,那么我们就可以直接在创建时选择枚举就可以. ...
- hashMap、ConcurrentHashMap、hashTable、TreeMap、LinkedHashMap用法区别详解
Java集合中设计了一个接口Java.util.Map,它实现类中hashMap.hashTable.TreeMap.ConcurrentHashMap.LinkedHashMap. Map类型的集合 ...
- 全栈交叉编译X86完成过程经验分享
1 CMAKE的交叉编译配置 主要是C和C++编译器的配置和SYSROOT的配置. set (CMAKE_SYSTEM_NAME "Linux") set (CMAKE_SYSTE ...
- Runable与Callable的区别
Runable与Callable的区别: public interface Callable<V> { V call() throws Exception;//V是Callable返回值的 ...
- 【机翻】RTnet – 灵活的硬实时网络框架
目录 RTnet – 灵活的硬实时网络框架 0 摘要 1 介绍 2 基础服务 2.1 数据包管理 2.2 UDP/IP 实现 2.3 Driver Layer 2.4 应用程序接口 2.5 捕获扩展 ...
- vue package.json 详解
示例: { "name": "scrm", "version": "0.1.0", "private" ...
- FreeRTOS --(16)资源管理之临界区
转载自 https://blog.csdn.net/zhoutaopower/article/details/107387427 临界区的概念在任何的 SoC 都存在,比如,针对一个寄存器,基本操作为 ...