一.过期策略

Redis 的过期策略主要是指管理和删除那些设定了过期时间的键,以确保内存的有效使用和数据的及时清理。 具体来说,Redis 有三种主要的过期策略:定期删除(Scheduled Deletion)、惰性删除(Lazy Deletion)和内存淘汰策略(Eviction Policies)。

1.1定期删除

Redis 的定期删除策略(Scheduled Deletion)的步骤如下:

1.设置定期任务:
  • Redis 会在后台线程中设置一个定期任务,用于扫描和删除过期键。这个任务会在特定的时间间隔内执行,默认每 100 毫秒运行一次。
2.随机抽样检查:
  • 在每次定期任务执行时,Redis 会从已设置过期时间的键中随机抽取一部分键进行检查,而不是一次性检查所有键。这样可以减少对 CPU 的负载。
3.检查键的过期状态:
  • 对于抽取的每一个键,Redis 会检查该键的过期时间。如果发现某个键已经过期,Redis 就会立即删除该键。
4.重复检查:
  • 如果在一次扫描中发现了删除的健的占比超过了设置的阈值(比如:25%),那么 Redis 会继续进行下一轮的随机抽样检查,直到在一次扫描中没有找到任何过期的键为止。
  • 调节扫描频率和负载:
  • 为了防止定期删除任务占用过多的 CPU 资源,Redis 会根据上一次扫描中删除的过期键的数量来动态调整下一次扫描的强度。比如,如果上一次扫描删除了大量的过期键,那么下一次扫描的键数量可能会减少,反之亦然。

1.2惰性删除

Redis 的惰性删除策略(Lazy Deletion)的步骤如下:

1.访问键被访问时检查过期状态:
  • 当客户端对 Redis 发起读操作(如 GET)或写操作(如 SET)时,Redis 首先会检查被访问的键是否设置了过期时间。
2.获取当前时间:
  • Redis 获取当前的系统时间,用来与键的过期时间进行比较。
3.比较过期时间:
  • 如果键的过期时间早于当前时间,说明该键已经过期。
4.删除过期键:
  • 如果键已经过期,Redis 会立即删除该键,并返回一个特殊的响应(如返回 nil 表示键不存在,或其他与操作类型相关的结果)。
5.处理未过期键:
  • 如果键未过期,Redis 会继续正常处理客户端的请求,返回键的值或执行相应的写操作。
示例

假设客户端向 Redis 发起一个 GET 操作来读取键 mykey 的值,惰性删除的详细步骤如下:

1.客户端发送 GET mykey:
  • 客户端向 Redis 发送 GET mykey 命令。
2.Redis 接收请求并查找键:
  • Redis 接收到请求后,查找键 mykey。
3.检查过期时间:
  • Redis 发现 mykey 设置了过期时间,获取当前时间。
4.比较当前时间和过期时间:
  • Redis 比较当前时间和 mykey 的过期时间。
  • 如果 mykey 的过期时间是 2024-06-01 12:00:00,当前时间是 2024-06-01 12:01:00,则 mykey 已经过期。
  • 如果当前时间是 2024-06-01 11:59:00,则 mykey 未过期。
5.删除过期键或返回值:
  • 如果 mykey 已经过期,Redis 立即删除 mykey,并返回 nil 表示键不存在。
  • 如果 mykey 未过期,Redis 返回 mykey 的值。

1.3内存淘汰

Redis 的内存淘汰策略(Eviction Policies)是指当 Redis 实例的内存使用达到配置的最大上限时,采取的一系列措施来删除现有的键,以腾出空间给新的数据。

以下是 Redis 内存淘汰策略的详细说明:

1. volatile-lru(使用 LRU 算法删除设置了过期时间的键)
  • 描述:从设置了过期时间的键中,使用 LRU(Least Recently Used,最近最少使用)算法删除最不常用的键。
  • 应用场景:适用于需要在有过期时间的键中保持最常用的数据的情况。
2. allkeys-lru(使用 LRU 算法删除所有键)
  • 描述:从所有键中,使用 LRU 算法删除最不常用的键。
  • 应用场景:适用于希望在所有数据中保持最常用的数据的情况,而不考虑键是否设置了过期时间。
3. volatile-random(随机删除设置了过期时间的键)
  • 描述:从设置了过期时间的键中,随机删除键。
  • 应用场景:适用于希望简单地随机删除有过期时间的键,以释放内存的情况。
4. allkeys-random(随机删除所有键)
  • 描述:从所有键中,随机删除键。
  • 应用场景:适用于希望简单地随机删除键,以释放内存的情况。
5. volatile-ttl(删除设置了过期时间且剩余存活时间最短的键)
  • 描述:从设置了过期时间的键中,删除剩余存活时间(TTL,Time to Live)最短的键。
  • 应用场景:适用于希望首先删除那些即将过期的键,以尽量保留长时间有效数据的情况。
6. noeviction(不删除任何键)
  • 描述:当内存使用达到上限时,不删除任何键,直接返回错误。
  • 应用场景:适用于希望严格控制内存使用,不希望自动删除任何键的情况。通常用于希望 Redis 只作为缓存或持久存储的一部分,并在达到内存上限时明确地处理错误。
具体操作步骤

以下是 Redis 内存淘汰策略的具体操作步骤:

1.达到内存上限:
  • Redis 接收到 SET mykey value 请求。
2.选择淘汰策略:
  • Redis 发现内存使用已达上限,触发淘汰策略。
3.执行淘汰策略
  • Redis 使用 LRU 算法,从所有键中选择最不常用的键。
4.释放内存
  • Redis 删除 LRU 列表中的最不常用键,如 oldkey1 和 oldkey2
5.继续处理请求
  • 删除键后释放足够内存,Redis 继续处理 SET mykey value 请求,并成功存储新键 mykey

二. 三种策略相互配合

2.1 正常情况下

  • 定期删除 和 惰性删除 是主要的过期数据清理手段。
  • 定期删除在后台自动清理一部分过期键,减少内存占用。
  • 惰性删除确保每次访问时,过期键都能被及时清理,保证数据的有效性。
2.2 内存不足时:
  • 先调用定期删除,删除一波过期数据
  • 如果内存得以缓解,到此结束
  • 如果内存依然不足,也不会再去可以删除过期数据,因为那样影响效率,需要遍历全部数据才能找全,全部的过期数据,因为Redis是单线程的,影响效率是致命的,所以无论是否还存在过期数据都会执行内存淘汰机制,保证一次性就可以缓解内存不足的问题,如果是遍历全部数据,目的是为了释放内存缓解内存不足,除了上述说的造成拉跨效率的问题,还可能即使删了全部过期数据,依然解决不了问题。

三.定时器

Redis并没有使用定时器来管理定时Key

3.1为什么不采用定时器来管理定时key?
  • 猜测:Redis是单进程的程序,想要完成上述猜想,就需要引用多线程,这与设计初心是相悖的
3.2如果想要实现定时器管理定时Key,该怎么设计定时器?

3.2.1基于优先级队列/堆

  • 在Redis的key过期场景中,过期时间越早,优先级越高,此时队首元素就是过期时间最早的,优先级最高的。此时定时器只需要分配一个线程,让这个线程检查队首元素,看是否过期。
  • 但是这个扫描线程不能扫描的太频繁(需要节省cpu开销),可以 根据当前时间和队首过期时间设置一个等待,当时间到了再唤醒这个等待。
  • 如果在队首元素休眠过程中,有了一个新的任务,新任务比队首元素更快过期,可以在新任务添加的过程中,唤醒刚才线程重新检查队首元素,再根据时间差重新调整阻塞时间。

3.2.2基于时间轮实现的定时器

  • 它可以高效地管理大量的定时任务。
  • 时间轮是一个环形的数据结构,分为多个槽(slot)。每个槽代表一个时间间隔,槽中存放着在该时间间隔内要执行的定时任务。时间轮按照固定的时间间隔进行旋转,当时间轮旋转到当前时间所在的槽时,就触发该槽中的定时任务。
  • 时间轮的实现流程如下:
  • 1. 初始化时间轮,确定时间间隔和槽数量。
  • 2. 将定时任务添加到时间轮中,计算任务应该添加到哪个槽。
  • 3. 时间轮按照固定的时间间隔开始旋转。
  • 4. 当时间轮旋转到当前时间所在的槽时,触发该槽中的定时任务。
  • 5. 定时任务执行完毕后,可以选择删除任务或重新添加到时间轮中。
  • 通过使用时间轮,定时器可以高效地管理大量的定时任务,避免了每个任务都需要单独计时的开销。同时,时间轮还可以支持动态添加和删除任务,提供了灵活性。

但是时间轮很难做到即时处理,比如说你定义五秒后删除,他可能在十几秒后才会删除,因为在执行到该代码部分时就可能已经过了十几秒,然后它会将这个时间轮的指针移动对应单位个数,但是再结合惰性删除,就不会被上层感知到,所以我觉得放在这个位置很合适,隔一定时间清理一次过期Key,但对于要求即时性的程序就不推荐时间轮的方法。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐