面试题答案
一键面试底层数据结构
Redis 使用字典(dict)来存储键值对,在这个字典中,每个键值对都可以关联一个过期时间。过期时间在 Redis 内部以毫秒精度的 Unix 时间戳形式存储在一个与键空间(存储键值对的字典)平行的过期字典(expires dict)中。过期字典的键是指向键空间中某个键对象的指针,值则是该键的过期时间戳。
算法
- 惰性删除:当客户端访问一个键时,Redis 首先检查该键是否存在于过期字典中。如果存在,且当前时间已经超过了过期时间,那么 Redis 会删除该键,并返回相应结果(例如,对于读操作返回 nil)。这种方式不会主动去扫描过期的键,只有在访问键时才进行检查,减少了对正常操作的影响。
- 定期删除:为了避免惰性删除导致过期键长时间占用内存,Redis 会定期执行删除操作。Redis 会随机抽取一定数量的键检查是否过期,并删除过期的键。具体做法是,Redis 会在每个事件循环中,根据配置的定期删除策略(hz 参数,每秒执行的周期数),每次随机抽取一些键进行过期检查和删除。
时间复杂度
- 设置过期时间(EXPIRE 命令):时间复杂度为 O(1)。因为在字典中设置一个键的过期时间,本质上就是在过期字典中添加一个键值对,而字典的插入操作平均时间复杂度为 O(1)。
- 惰性删除:时间复杂度为 O(1)。检查键是否过期并删除,都是基于字典的查找和删除操作,平均时间复杂度为 O(1)。
- 定期删除:每次定期删除操作时间复杂度为 O(N),其中 N 是每次随机检查的键的数量。但由于每次检查的键数量有限,且分散在事件循环中执行,对整体性能影响较小。
高并发场景下的优化
- 优化定期删除策略:
- 适当增加定期删除的执行频率(调整 hz 参数),这样可以更及时地清理过期键,但会增加 CPU 负担。需要根据实际业务场景和服务器性能进行权衡。
- 调整每次检查的键数量,在保证能有效清理过期键的同时,避免过多占用 CPU 资源。
- 使用集群:采用 Redis 集群模式,将数据分布到多个节点上,减少单个节点的过期键管理压力。每个节点独立进行过期键的管理,从而提高整体系统的性能和可扩展性。
- 异步删除:对于一些大键值对的删除操作,可以使用 Redis 的异步删除命令(如 UNLINK)。这样删除操作会被放入后台线程执行,不会阻塞主线程,从而避免在高并发场景下因删除大键值对导致的性能问题。
- 优化业务逻辑:尽量减少对过期键的不必要访问,避免因为频繁访问过期键触发惰性删除操作带来的性能开销。例如,可以在业务层面缓存一些数据,减少对 Redis 的直接访问频率。