当前位置:首页 > 问答 > 正文

怎么实时监听Redis过期事件,订阅提醒那块怎么搞比较方便

要实时监听Redis的过期事件,并在过期时收到提醒,核心思路是开启Redis本身的一个叫做“键空间通知”的功能,然后让你的应用程序像一个订阅者一样,去订阅这个频道,等着接收Redis发过来的消息。

第一部分:让Redis能够发出通知(配置Redis)

你得告诉Redis:“嘿,请你在有键过期的时候,发个消息通知我。” 这个功能默认是关闭的,因为开启它会消耗一点点CPU资源,你需要修改Redis的配置文件(通常是 redis.conf)。

在配置文件里,找到关于 notify-keyspace-events 的配置项,它可能被注释掉了(行首有个#号),你需要把它改成类似下面的样子:

notify-keyspace-events Ex

这里解释一下这几个字母的意思(虽然你拒绝专业化术语,但这个简单了解有助于你理解):

怎么实时监听Redis过期事件,订阅提醒那块怎么搞比较方便

  • E 代表开启键空间事件通知。
  • x 代表只关心过期事件(expired)。

你也可以用更简单的 notify-keyspace-events AKE,这里的 A 代表所有事件,K 代表键空间事件,E 代表键事件,这样配置后,不仅仅是过期,任何对键的增删改操作都会通知,信息量会比较大,对于只关心过期事件来说,用 Ex 就足够了。

修改完配置后,你需要重启Redis服务,或者如果是在命令行里,可以用 CONFIG SET notify-keyspace-events Ex 这个命令来临时生效(重启后会失效,永久生效还是要改配置文件)。

根据一篇名为《Redis键空间通知实现延迟任务》的博客文章提到,这是实现监听的第一步,必须确保这个配置是正确的。

第二部分:让你的程序成为订阅者(编写客户端代码)

Redis配置好后,它就会像一个广播电台,现在你需要编写一个一直运行着的程序,像收音机一样调到正确的频道,等着收听过期消息。

怎么实时监听Redis过期事件,订阅提醒那块怎么搞比较方便

这个“频道”在Redis里有固定的命名规则,对于过期事件,频道名是 __keyevent@0__:expired,这里的 0 指的是第0号数据库(Redis默认有16个数据库,从0开始),如果你的键在其他数据库,需要相应修改。

用你熟悉的编程语言来实现这个订阅者,这里用Python语言举例,因为它比较易懂。

你需要一个Redis客户端库,redis-py

import redis
import time
# 连接到Redis服务器
r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
# 创建一个订阅对象,并订阅特定的频道
pubsub = r.pubsub()
pubsub.psubscribe('__keyevent@0__:expired')  # 使用psubscribe可以进行模式匹配,更灵活
print("开始监听Redis键过期事件...")
# 持续监听消息
for message in pubsub.listen():
    # message是一个字典,包含频道、模式、数据等信息
    if message['type'] == 'pmessage':  # 如果是模式订阅收到的消息
        # 过期的那个键的名字,就在 message['data'] 里
        expired_key = message['data']
        print(f"检测到键过期了: {expired_key}")
        # 在这里编写你的业务逻辑,比如发邮件、发微信通知、调用另一个接口等等
        # if expired_key.startswith("order_"): send_order_timeout_alert(expired_key)

这段代码做的事情是:

  1. 连接Redis。
  2. 创建一个订阅对象。
  3. 订阅了过期事件的频道,这里用了 psubscribe(模式订阅),它允许使用通配符,虽然这里频道名是固定的,但这是个好习惯。
  4. 然后进入一个无限的循环 pubsub.listen(),这个循环会一直阻塞,直到有消息到来。
  5. 当有键过期时,Redis会推送消息到这个循环里,我们就能从 message['data'] 中拿到那个过期的键的名字。

第三部分:怎么搞比较方便和一些注意事项

怎么实时监听Redis过期事件,订阅提醒那块怎么搞比较方便

  1. 确保订阅程序的稳定性:这个订阅程序必须7x24小时不间断运行,如果它挂掉了,那么在它宕机期间发生的所有过期事件你就都收不到了,你需要把它部署在一个可靠的环境里,比如用Docker容器管理,或者用Supervisor这类进程守护工具,确保它崩溃后能自动重启。

  2. 处理消息的幂等性:所谓幂等性,简单说就是同一条消息你处理一次和处理多次的结果应该是一样的,因为网络问题或者你的程序问题,有可能同一条过期通知你可能会收到多次,在你的处理逻辑里(print(f"检测到键过期了: {expired_key}") 下面那部分),最好要考虑到这一点,根据过期的键ID去数据库检查状态,如果已经是“已过期”状态,就不要再重复处理了。

  3. 事件不是精确的:Redis的过期事件不是完全精确到秒的,Redis的过期策略是惰性删除和定期删除结合,意思是,一个键到期后,不一定马上就会被Redis删除并发出通知,可能会有一个微小的延迟,如果你的业务场景要求秒级的精确度,这个方案可能有一点点风险,但对于大多数提醒类场景(比如订单30分钟未支付关闭),这个延迟是完全可接受的,根据Redis官方文档对过期时间的解释,这是由其内部机制决定的。

  4. 消息里只有键名:非常重要的一点是,过期事件消息里只包含那个过期的键的名字(Key)不包含这个键原来对应的值(Value),如果你需要在处理过期事件时用到原来的值,你有两个选择:

    • 方案一:在设置这个键的时候,把必要的业务信息直接编码在键名里,不要设一个键叫 order_12345,而是设成 order_12345_userId_67890_amount_100,这样过期时你从键名就能解析出需要的信息,但这样键名会很长。
    • 方案二:在设置键的同时,把完整的值存到另一个不会过期的键里,设置 order:12345 的同时,把订单详情存到 order:detail:12345 里,当 order:12345 过期时,你收到通知,再去 order:detail:12345 里取出详细信息进行处理,处理完再删除这个详情键。
  5. 选择成熟的库:无论你用Java(如Jedis、Lettuce)、Go(go-redis)、Node.js(ioredis)还是其他语言,都选择社区活跃、支持Pub/Sub功能的客户端库,它们的核心思想和上面的Python例子都是相通的。

最方便的做法就是:配置好Redis -> 写一个稳定运行的订阅程序 -> 在程序里根据收到的过期键名去执行你的提醒逻辑,只要注意了上面提到的稳定性和数据问题,这就是一个非常直接且有效的解决方案。