永远不要使用Redis过期监听实现定时任务!

VSole2022-07-03 17:01:34

前言

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

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

时间轮

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

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

| 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
本作品采用《CC 协议》,转载必须注明作者和本文链接
前言日前拜读阿牛老师的大作《领导:谁再用定时任务实现关闭订单,立马滚蛋!》发现其方案有若干瑕疵,特此抛砖引玉讨论一二。在使用 Redisson DelayQueue 等定时任务中间件时可以同时使用扫描数据库的方法作为补偿机制,避免中间件故障造成任务丢失。
.Net之延迟队列
2022-07-27 17:54:49
介绍具有队列的特性,再给它附加一个延迟消费队列消息的功能,也就是说可以指定队列中的消息在哪个时间点被消费。
同时又在默认的系统环境下,通过pip安装部署,大多数情况下,是不会出现问题。相关的依赖库,在Python安装之后,都是通过pip来安装。但实际我们装个python2.7版本的环境就基本满足目前的需求。我们创建了一个叫做py27的虚拟环境。workonpy272.4 安装Opencananrypip在安装opencanary时,会自动安装他所需求要的各种依赖,一般不出问题的话,一切都会顺利安装完成。
现在我们手上有外网路由器的管理权限还有内网无线ap的权限,外网路由器可以telnet、ping探测IP和端口存活、nat端口映射。总算找到一个可以打的端口,这里在华三路由器telnet过去我不知道为啥我输入info没有返回未授权的信息,映射到外网输入info是直接返回未授权,直接端口映射到外网直接用fscan打一下。芜湖,fscan创建个计划任务弹到vps上面。
鉴于世上有着许多的 Linux 专家和开发者,显然还存在其他的网络监控工具,但在这篇教程中,我不打算将它们所有包括在内。它按每个进程来分组带宽,而不是像大多数的工具那样按照每个协议或每个子网来划分流量。对我而言, nethogs 是非常容易使用的,或许是因为我非常喜欢它,以至于我总是在我的 Ubuntu 12.04 LTS 机器中使用它来监控我的网络带宽。例如要想使用混杂模式来嗅探,可以像下面展示的命令那样使用选项 -p:nethogs -p wlan0
用户名:加密密码:密码最后一次修改日期:两次密码的修改时间间隔:密码有效期:密码修改到期到的警告天数:密码过期之后的宽限天数:账号失效时间:保留。查看下pid所对应的进程文件路径,
一文读懂HW护网行动
2022-07-26 12:00:00
随着《网络安全法》和《等级保护制度条例2.0》的颁布,国内企业的网络安全建设需与时俱进,要更加注重业务场景的安全性并合理部署网络安全硬件产品,严防死守“网络安全”底线。“HW行动”大幕开启,国联易安誓为政府、企事业单位网络安全护航!
安全区域边界在近几年变得越来越精细越来越模糊,因为攻击的形式、病毒传播的途径层出不穷,我以攻击者的角度去看,任何一个漏洞都可以成为勒索病毒传播和利用的方式,我们要做到全面补丁压力重重,通过边界划分,依靠不同的边界安全防护,在发生问题的情况下将损失降到最低。
痛苦的纯文本日志管理日子一去不复返了。虽然纯文本数据在某些情况下仍然很有用,但是在进行扩展分析以收集有洞察力的基础设施数据并改进代码质量时,寻找一个可靠的日志管理解决方案是值得的,该解决方案可以增强业务工作流的能力。 日志不是一件容易处理的事情,但无论如何都是任何生产系统的一个重要方面。当您面临一个困难的问题时,使用日志管理解决方案要比在遍布系统环境的无休止的文本文件循环中穿梭容易得多。
VSole
网络安全专家