侧边栏壁纸
  • 累计撰写 244 篇文章
  • 累计创建 16 个标签
  • 累计收到 0 条评论
隐藏侧边栏

Redis 过期删除策略(二):通过懒惰删除异步删除过期键

kaixindeken
2021-04-28 / 0 评论 / 0 点赞 / 66 阅读 / 1,132 字

被动删除的引入

Redis 会通过异步线程定期检查过期键字典并主动删除过期键,但是这个主动删除并不会全量扫描所有过期键,而是采用了贪心策略,分多次清理所有过期键,这当然是为了性能考虑,但是这么做也存在一个显而易见的问题:存在内存泄露的风险,即某些键已经过期,但是被主动删除策略漏掉了。

如果客户端试图访问一个过期键却依然可以返回对应键值,这肯定是不允许的,属于功能上的 bug 了,所以除了定期扫描主动删除策略外,Redis 还提供了与之配套的被动删除策略:当客户端访问某个键时(获取键值、判断是否存在等),先判断这个键是否已过期,如果过期则将其删除。这样一来,对于已过期的键,即使没有被定期扫描命中删除,依然会在访问到它的时候对其进行删除操作,从而保证了 Redis 过期策略得以正常运转。

懒惰删除及其底层实现

对于字符串类型的键值而言,删除操作可以很快完成,但是如果是复合型数据结构,比如列表、集合、字典等,某个键对应的键值非常大,有成千上万个元素,在主线程中进行删除操作,就可能导致主线程阻塞,进而影响 Redis 性能,所以从 Redis 4.0 开始引入了 UNLINK 指令,采用和定期删除一样的异步线程来处理键的非阻塞删除工作。

在此之前,也不是直接删除,而是采用了和渐进式 rehash 类似的逐步删除,但是这种处理非常复杂,需要会每种数据结构编写不同的回收策略,还要控制回收频率。

具体实现是主线程从全局哈希表中将待删除的键摘除,扔到到队列里(通过双向链表实现),然后异步线程从该队列中获取待删除键进行异步删除工作,这样,既可以保证主线程能够高性能处理客户端请求,又能保证过期键不再被访问到。

我们把这样的异步删除键的行为称之为懒惰删除(lazy free),或者叫惰性删除,因为它不是在主线程中同步删除,而是通过消费队列异步处理的,并且为了提高处理效率,支持通过多线程消费包含待删除键的队列。

由于这个队列会被主线程和异步线程同时操作,所以需要是线程安全的,Redis 底层是通过锁来保证并发安全的:当主线程往队列推送任务时,加锁,推送完毕后,释放锁并唤醒阻塞的异步线程来消费队列;异步线程通过轮询消费队列,从队列读取任务时,也会加锁,读取完毕后解锁,如果队列中没有任何任务,异步线程会阻塞(相关源码可以在 Redis 底层 bio.c 中看到)。

当然了,如果键值本身很小,调用 UNLINK 指令时就不需要像上面这样延后处理了,直接回收对应的内存空间即可,就像 DEL 指令那样。

另外,除了键的过期删除外,FLUSHDB、FLUSHALL、LRU 淘汰策略之类的操作也会用到懒惰删除来提升系统性能。

0

评论区