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

redis里那种自动销毁回调函数怎么弄,感觉挺实用的自销毁机制讲解

在Redis里,你说的这个“自动销毁回调函数”是一个非常实用的功能,它的大名叫做“键空间通知”,具体到我们关心的自动销毁,就是其中的“键空间事件”下的“过期事件”,这个功能说白了就是:当Redis里某个带过期时间的key活到寿了,被系统自动删除的时候,你能够让Redis给指定的渠道发送一个“通知”,然后你的程序可以捕捉到这个通知,接着执行你自己想做的事情。

(来源:Redis官方文档对Keyspace Notifications的说明)

这个机制是怎么运作的?

它不像一些编程语言里的析构函数那样,直接在保存数据的同一个地方执行代码,Redis为了保持自身的简单性和高性能,采用的是“发消息”的间接方式,整个流程可以拆解成这样几步:

  1. 你设置一个会过期的键:比如你在Redis里执行命令 SET mykey "hello" EX 10,这就意味着这个叫mykey的键,10秒钟后会自动消失。
  2. Redis内部计时并删除:Redis会自己管理这些过期键,时间一到,就在内部把它删掉。
  3. 触发事件:当删除动作发生的那一刻,Redis会生成一个事件消息,这个消息的内容大概是:“注意!注意!mykey这个键因为过期(expire)被删掉了!”
  4. 发布消息:Redis把这个事件消息发布到一个内部的“频道”里,这个频道是Redis的Pub/Sub(发布/订阅)系统的一部分,但它是内部的,对使用者是透明的。
  5. 你的程序接收通知:你的应用程序需要事先像一个听众一样,去“订阅”这个频道,专门监听这类过期事件,当消息发布出来时,你的程序就能立刻接收到。

(来源:Redis官方文档对事件生成和Pub/Sub机制的描述)

怎么才能用上这个功能?

这个功能在Redis里默认是关闭的,因为它会消耗一点点额外的CPU资源,而且如果你不关心这些事件,开启它也是浪费,所以你需要先进行配置。

  • 第一步:配置Redis服务器 你需要修改Redis的配置文件(redis.conf),或者通过命令行临时设置(重启会失效),关键参数是 notify-keyspace-events。 这个参数的值是一些字母组合,每个字母代表一种你想监听的事件类型,对于我们想要的“键过期”事件,主要用到两个:
    • K:表示启用键空间事件通知。
    • E:表示启用键事件事件通知。
    • x:表示监听过期类型的事件(expired)。 最常见的设置是 Ex,这意味着:“请通知我所有与键过期相关的事件。” 你可以通过命令临时设置:CONFIG SET notify-keyspace-events Ex

(来源:Redis官方文档中关于notify-keyspace-events配置参数的详细解释)

  • 第二步:在你的程序中订阅事件 配置好Redis后,你的应用程序就需要扮演一个订阅者的角色,具体代码因编程语言而异,但思路都一样,以Python为例,使用redis-py库:

    import redis
    import threading
    # 创建一个Redis连接
    r = redis.Redis(host='localhost', port=6379, db=0)
    # 定义一个处理收到消息的函数
    def event_handler(msg):
        # msg是一个字典,包含频道和信息等数据
        print(f"收到消息: {msg}")
        # 通常msg['data']里包含了被删除的key的名字和一些操作类型
        # 实际格式是类似:'expired mykey' 这样的字符串,你需要解析它
        if msg['type'] == 'message':
            # 解析出key的名称
            channel, key = msg['channel'], msg['data']
            print(f"键 {key.decode()} 已过期,来自频道 {channel.decode()}")
    # 创建一个订阅对象,并订阅特定的频道
    # 过期事件发布的频道名是 __keyevent@0__:expired
    # 其中0是数据库编号,如果你用的不是0号库,需要相应修改
    pubsub = r.pubsub()
    pubsub.psubscribe('__keyevent@0__:expired')  # 使用模式订阅更通用
    # 在一个新线程中开始监听(避免阻塞主线程)
    thread = pubsub.run_in_thread(sleep_time=0.01, daemon=True)
    # 你可以设置一个测试键了
    r.setex('test_key', 5, '这个值5秒后过期')  # 5秒后过期
    # 主程序继续做其他事情...
    print("程序开始监听过期事件,等待5秒...")
    # 保持主线程运行,以便能看到回调
    import time
    time.sleep(10)

(来源:redis-py库的官方文档中对Pub/Sub用法的示例)

这个自销毁回调到底实用在哪?

这其实就是发挥想象力的地方了:

  1. 会话(Session)管理:用户登录你的网站,你会在Redis存一个session数据,并设置30分钟过期,当session过期时,你可以通过这个回调,去清理数据库里这个用户的临时状态,或者记录一条“用户超时下线”的日志。
  2. 缓存数据同步:你用Redis缓存了一些复杂的计算结果,当缓存过期被删除时,你的回调函数可以触发一个任务,去预先计算新的结果并重新加载到缓存中,实现“延迟计算”或“缓存预热”,这样用户下次请求时就不用等了。
  3. 分布式锁的保底释放:你用Redis实现分布式锁,一般会设置一个过期时间防止死锁,虽然理想情况是业务完成后主动释放锁,但万一程序崩溃没来得及释放呢?这个过期事件可以作为一个安全网,当锁过期时,通知监控系统报警,或者执行一些资源清理操作,告诉你“有个锁异常过期了,可能出问题了,快查查!”
  4. 限流器重置:比如你做了一个每分钟最多100次请求的限流器,用Redis键的过期时间来实现每分钟重置,当键过期时,回调函数可以帮你记录这一分钟的最终请求量,或者初始化下一个时间窗口的计数器。

需要注意的几个坑

  • 不是百分百可靠:Redis的过期事件通知是“尽力而为”的,它不能保证事件一定被送达,比如如果你的订阅者在事件发生时恰好断线了,那么就可能错过这个通知,所以它不适合用于要求绝对可靠性的关键业务逻辑,更多是用于辅助性的清理、统计和告警。
  • 性能开销:开启这个功能后,Redis每次删除过期键都会多一步“发布消息”的操作,如果你的系统有海量的键同时过期,可能会对性能有细微影响。
  • 消息的解析:你收到的消息是一个原始的字符串,需要自己按照Redis约定的格式去解析,才能提取出有用的信息(比如是哪个键过期了)。

Redis这个自销毁回调机制,通过“发布订阅”的模式,巧妙地在不破坏核心数据结构简单性的前提下,提供了一个非常实用的扩展点,只要你理解了它的工作原理和局限性,就能在很多场景下用它来实现优雅的自动化处理逻辑。

redis里那种自动销毁回调函数怎么弄,感觉挺实用的自销毁机制讲解