【面试题】Redis过期删除与内存淘汰-LMLPHP

Redis过期删除策略

🙎‍♂️面试官:如何设置key的过期时间?

🙋‍♂答:

expire <key> <n> 
// 设置 key 在 n 秒后过期,比如 expire key 100 表示设置 key 在 100 秒后过期
set <key> <value> ex <n> 
// 设置键值对的时候,同时指定过期时间(精确到秒)
TTL <key>
// 查看过期时间
PERSIST <key>
// 取消过期时间

🙎‍♂️面试官:什么是Redis过期删除策略?

🙋‍♂答:

过期删除策略就是删除过期键值对采用的方法。Redis采用惰性删除 + 定期删除两种策略。

  • 惰性删除:就是在每次访问key的时候,都先对key的过期时间进行检查。
    如果过期,就删除,返回null给客户端;没有过期就正常返回。
  • 定期删除:会每隔10S 检查一次数据库,先从过期字典中随机抽取20个key,检查这20个key是否过期。
    如果key的过期数量大于25%,继续重复进行检查;反之停止继续删除。

🙎‍♂️面试官:过期的key存放到哪里/如何判断key是否过期?

🙋‍♂答:

Redis中有一个过期字典,来保存有过期时间的key。所以在查找key时会先查找字典。
过期字典是一个哈希表,查找key的时间复杂度是O(1)。

查询key时,如果key在过期字典中,会将过期的时间与系统时间进行比较,如果比系统时间大,那就没有过期,否则判断已经过期。

  • 过期字典的 key 是一个指针,指向某个键对象;
  • 过期字典的 value 是一个 long long 类型的整数,这个整数保存了 key 的过期时间;

🙎‍♂️面试官:Redis具体是怎样实现过期删除策略的?

🙋‍♂答:

定期删除策略中:

  • 定期删除时间在redis.conf中进行配置,hz默认就是10s。
  • 随机抽查数量为20个key,是写死在代码里的。

内存淘汰策略

🙎‍♂️面试官:为什么有过期删除策略还要有内存淘汰机制?

🙋‍♂答:

不论是定期删除策略还是惰性删除机制,都不是一种完全精准的删除,还是会存在key没有被删除的场景,所以需要内存淘汰机制。

当Redis的运行内存超过了我们设置的阈值,就会触发内存淘汰机制。

🙎‍♂️面试官:常见的内存淘汰策略有几种?

🙋‍♂答:

Redis的内存淘汰机制有八种。

在进行数据淘汰的策略中,分为两种类型:

  • 在设置了过期时间的数据中进行淘汰:
    • volatile【短暂】-random随机淘汰设置了过期时间的任意键值;
    • volatile-ttl(Time-To-Live):优先淘汰更早过期的键值;
    • volatile-lru(Least recently used,最近最少使用)(Redis3.0 之前,默认的内存淘汰策略):淘汰所有设置了过期时间的键值中,优先淘汰最久未用的键值;
    • volatile-lfu(Least Frequently Used,最近最不常用)(Redis 4.0 后新增的内存淘汰策略):淘汰所有设置了过期时间的键值中,优先淘汰最近最不常用的键值;
  • 在所有数据范围内进行淘汰:
    • allkeys-random:随机淘汰任意键值;
    • allkeys-lru:淘汰整个键值中最久未用的键值;
    • allkeys-lfu(Redis 4.0 后新增的内存淘汰策略):淘汰整个键值中最不常用的键值。

noeviction(不进行驱逐)(Redis3.0之后,默认的内存淘汰策略) :表示不进行内存淘汰,而是不提供服务。直接返回错误。

🙎‍♂️面试官:Redis中,LRU 算法和 LFU 算法有什么区别?

🙋‍♂答:

传统 LRU 算法使用链表实现,链表中的元素按照操作顺序从前往后排列,最新操作的键会被移动到表头,当需要内存淘汰时,删除的链表尾部元素就代表最久未被使用的元素。

但是Redis 并没有使用这样的方式实现 LRU 算法,因为传统的 LRU 算法存在两个问题:

  • 用链表管理Redis中的缓存数据,会有空间开销。
  • 当有数据被访问时,需要在链表上把该数据移动到头端,如果有大量数据被访问,就会带来很多链表移动操作,会很耗时,进而会降低 Redis 缓存性能。

Redis中的实现如下,LRU(最近最少使用)算法根据时间淘汰,LFU(最近最不常用)算法根据使用次数淘汰。

  • 在 LRU 算法中,Redis 对象头的 24 bits 的 lru 字段是用来记录 key 的访问时间戳,因此在 LRU 模式下,Redis可以根据对象头中的 lru 字段记录的值,来比较最后一次 key 的访问时间戳,从而淘汰最久未被使用的 key。
    • Redis 实现的 LRU 算法的优点:
      • 不用为所有的数据维护一个大链表,节省了空间占用;
      • 不用在每次数据访问时都移动链表项,提升了缓存的性能;

但是 LRU 算法有一个问题,无法解决缓存污染问题,比如应用一次读取了大量的数据,而这些数据只会被读取这一次,那么这些数据会留存在 Redis 缓存中很长一段时间,造成缓存污染。LFU 内存淘汰算法是 Redis 4.0 之后新增内存淘汰策略,用来解决 LRU 算法的问题。

LFU 全称是 Least Frequently Used 翻译为最近最不常用的,LFU 算法是根据数据访问次数来淘汰数据的,它的核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。

所以, LFU 算法会记录每个数据的访问次数。当一个数据被再次访问时,就会增加该数据的访问次数。这样就解决了偶尔被访问一次之后,数据留存在缓存中很长一段时间的问题,相比于 LRU 算法也更合理一些。

  • 在 LFU 算法中,Redis对象头的 24 bits 的 lru 字段被分成两段来存储,高 16bit 存储 ldt(Last Decrement Time),低 8bit 存储 logc(Logistic Counter)。
    • ldt 是用来记录 key 的访问时间戳;
    • logc 是用来记录 key 的访问频次,它的值越小表示使用频率越低,越容易淘汰,每个新加入的 key 的logc 初始值为 5。logc 并不是单纯的访问次数,而是访问频次(访问频率),因为 logc 会随时间推移而衰减的
05-22 01:57