目录标题
- 过期删除策略
- 如何设置过期时间
- 查看key过期时间
- 取消key过期时间
- 如果判定key已过期
- 过期删除策略
- 定时删除
- 惰性删除
- 定期删除
- Redis过期删除策略
- Redis如何实现定期删除
- 第一种版本
- 第二种版本
- 内存淘汰策略
- 如何设置Redis最大运行内存
- 第一种版本
- 第二种版本
- Redis内存淘汰策略
- 不进行数据淘汰的策略
- 进行数据淘汰的策略
- 查看当前内存淘汰策略
- 修改内存淘汰策略
- LRU和LFU算法的区别
- Redis如何实现LRU算法
- 第一种版本
- 第二种版本
- Redis如何实现LFU算法
过期删除策略
如何设置过期时间
设置 key 过期时间的命令一共有 4 个:
expire <key> <n>
:设置 key 在 n 秒后过期,比如 expire key 100 表示设置 key 在 100 秒后过期;pexpire <key> <n>
:设置 key 在 n 毫秒后过期,比如 pexpire key2 100000 表示设置 key2 在 100000 毫秒(100 秒)后过期。expireat <key> <n>
:设置 key 在某个时间戳(精确到秒)之后过期,比如 expireat key3 1655654400 表示 key3 在时间戳 1655654400 后过期(精确到秒);pexpireat <key> <n>
:设置 key 在某个时间戳(精确到毫秒)之后过期,比如 pexpireat key4 1655654400000 表示 key4 在时间戳 1655654400000 后过期(精确到毫秒)
当然,在设置字符串时,也可以同时对 key 设置过期时间,共有 3 种命令:
set <key> <value> ex <n>
:设置键值对的时候,同时指定过期时间(精确到秒);set <key> <value> px <n>
:设置键值对的时候,同时指定过期时间(精确到毫秒);setex <key> <n> <valule>
:设置键值对的时候,同时指定过期时间(精确到秒)。
查看key过期时间
若查询结果为-1,则表示永不过期
ttl key
取消key过期时间
persist key
如果判定key已过期
每当我们对一个 key 设置了过期时间时,Redis 会把该 key 带上过期时间存储到一个过期字典(expires dict)中,也就是说「过期字典」保存了数据库中所有 key 的过期时间。过期字典存储在 redisDb 结构中,如下:
typedef struct redisDb {dict *dict; /* 数据库键空间,存放着所有的键值对 */dict *expires; /* 键的过期时间 */....
} redisDb;
过期字典数据结构结构如下:
- 过期字典的 key 是一个指针,指向某个键对象;
- 过期字典的 value 是一个 long long 类型的整数,这个整数保存了 key 的过期时间;
字典实际上是哈希表,哈希表的最大好处就是让我们可以用 O(1) 的时间复杂度来快速查找。当我们查询一个 key 时,Redis 首先检查该 key 是否存在于过期字典中:
- 如果不在,则正常读取键值;
- 如果存在,则会获取该 key 的过期时间,然后与当前系统时间进行比对,如果比系统时间大,那就没有过期,否则判定该 key 已过期。
过期删除策略
定时删除
定时删除策略的做法是,在设置 key 的过期时间时,同时创建一个定时事件,当时间到达时,由事件处理器自动执行 key 的删除操作。定时删除策略的优点:
- 可以保证过期 key 会被尽快删除,也就是内存可以被尽快地释放。因此,定时删除对内存是最友好的。
定时删除策略的缺点:
- 在过期 key 比较多的情况下,删除过期 key 可能会占用相当一部分 CPU 时间,在内存不紧张但 CPU 时间紧张的情况下,将 CPU 时间用于删除和当前任务无关的过期键上,无疑会对服务器的响应时间和吞吐量造成影响。所以,定时删除策略对 CPU 不友好。
惰性删除
惰性删除策略的做法是,不主动删除过期键,每次从数据库访问 key 时,都检测 key 是否过期,如果过期则删除该 key。
惰性删除策略的优点:
- 因为每次访问时,才会检查 key 是否过期,所以此策略只会使用很少的系统资源,因此,惰性删除策略对 CPU 时间最友好。
惰性删除策略的缺点:
- 如果一个 key 已经过期,而这个 key 又仍然保留在数据库中,那么只要这个过期 key 一直没有被访问,它所占用的内存就不会释放,造成了一定的内存空间浪费。所以,惰性删除策略对内存不友好。
定期删除
定期删除策略的做法是,每隔一段时间「随机」从数据库中取出一定数量的 key 进行检查,并删除其中的过期key。
定期删除策略的优点:
- 通过限制删除操作执行的时长和频率,来减少删除操作对 CPU 的影响,同时也能删除一部分过期的数据减少了过期键对空间的无效占用。
定期删除策略的缺点:
- 内存清理方面没有定时删除效果好,同时没有惰性删除使用的系统资源少。
- 难以确定删除操作执行的时长和频率。如果执行的太频繁,定期删除策略变得和定时删除策略一样,对CPU不友好;如果执行的太少,那又和惰性删除一样了,过期 key 占用的内存不会及时得到释放。
Redis过期删除策略
Redis 选择「惰性删除+定期删除」这两种策略配和使用,以求在合理使用 CPU 时间和避免内存浪费之间取得平衡。
Redis如何实现定期删除
第一种版本
1、这个间隔检查的时间是多长呢?
在 Redis 中,默认每秒进行 10 次过期检查一次数据库,此配置可通过 Redis 的配置文件 redis.conf 进行配置,配置键为 hz 它的默认值是 hz 10。特别强调下,每次检查数据库并不是遍历过期字典中的所有 key,而是从数据库中随机抽取一定数量的 key 进行过期检查。
2、随机抽查的数量是多少呢?
我查了下源码,定期删除的实现在 expire.c 文件下的 activeExpireCycle
函数中,其中随机抽查的数量由 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP
定义的,它是写死在代码中的,数值是 20。也就是说,数据库每轮抽查时,会随机选择 20 个 key 判断是否过期。接下来,详细说说 Redis 的定期删除的流程:
- 从过期字典中随机抽取 20 个 key;
- 检查这 20 个 key 是否过期,并删除已过期的 key;
- 如果本轮检查的已过期 key 的数量,超过 5 个(20/4),也就是「已过期 key 的数量」占比「随机抽取 key 的数量」大于 25%,则继续重复步骤 1;如果已过期的 key 比例小于 25%,则停止继续删除过期 key,然后等待下一轮再检查。
可以看到,定期删除是一个循环的流程。那 Redis 为了保证定期删除不会出现循环过度,导致线程卡死现象,为此增加了定期删除循环流程的时间上限,默认不会超过 25ms。
第二种版本
- Redis 维护了一个字典结构,用于存储键和键的过期时间。字典中的每个键都会关联一个过期时间,过期时间存储在键的值中。
- Redis 使用一个全局变量
hz
来表示每秒执行的定时器触发次数。默认情况下,hz
的值为 10,即每秒触发定时器 10 次。 - 每当定时器触发时,Redis 会随机选择一部分键进行检查。检查的键数量由配置参数
hz
决定。 - 对于每个被检查的键,Redis 会检查键的过期时间是否小于当前时间。如果是,则将该键标记为过期。
- 定期删除的过程并不会立即删除过期键。而是将过期键添加到一个待删除的链表中。
- 当执行写操作(如 GET、SET)时,Redis 会先检查待删除链表中的键是否过期。如果过期,则将其从数据结构中删除,并释放相关资源。
内存淘汰策略
过期删除策略,是删除已过期的 key,而当 Redis 的运行内存已经超过 Redis 设置的最大内存之后,则会使用内存淘汰策略删除符合条件的 key,以此来保障 Redis 高效的运行。
如何设置Redis最大运行内存
第一种版本
在配置文件 redis.conf 中,可以通过参数 maxmemory <bytes>
来设定最大运行内存,只有在 Redis 的运行内存达到了我们设置的最大运行内存,才会触发内存淘汰策略。 不同位数的操作系统,maxmemory 的默认值是不同的:
- 在 64 位操作系统中,maxmemory 的默认值是 0,表示没有内存大小限制,那么不管用户存放多少数据到 Redis 中,Redis 也不会对可用内存进行检查,直到 Redis 实例因内存不足而崩溃也无作为。
- 在 32 位操作系统中,maxmemory 的默认值是 3G,因为 32 位的机器最大只支持 4GB 的内存,而系统本身就需要一定的内存资源来支持运行,所以 32 位操作系统限制最大 3 GB 的可用内存是非常合理的,这样可以避免因为内存不足而导致 Redis 实例崩溃。
第二种版本
在 Redis 中,可以通过配置文件或在启动 Redis 时使用命令行参数来设置最大运行内存。
- 通过配置文件设置最大运行内存:
- 找到 Redis 的配置文件
redis.conf
。 - 在配置文件中找到
maxmemory
参数。 - 将
maxmemory
的值设置为期望的最大运行内存大小,单位可以是字节(例如maxmemory 1gb
)或以其他更友好的格式表示(例如maxmemory 2GB
)。 - 保存配置文件并重新启动 Redis。
- 找到 Redis 的配置文件
- 通过命令行参数设置最大运行内存:
- 在启动 Redis 时,使用
--maxmemory
参数指定最大运行内存的大小。例如redis-server --maxmemory 1gb
。 - 启动 Redis 时,还可以使用其他相关参数,如
--maxmemory-policy
来指定内存达到上限后的策略。
- 在启动 Redis 时,使用
需要注意的是,Redis 的最大运行内存设置的是 Redis 实例可使用的最大内存大小,但实际的内存使用情况可能会略微超过该值,因为 Redis 还需要一些内存来存储数据结构和执行内部操作。在实际应用中,应根据可用的硬件资源、数据量和性能需求来设置最大运行内存。过小的内存限制可能导致数据被淘汰或频繁的内存回收操作,而过大的内存限制可能浪费资源并导致 Redis 运行不稳定。因此,需要根据实际情况进行适当的调优。
Redis内存淘汰策略
不进行数据淘汰的策略
noeviction(Redis3.0之后,默认的内存淘汰策略) :它表示当运行内存超过最大设置内存时,不淘汰任何数据,这时如果有新的数据写入,则会触发 OOM,但是如果没用数据写入的话,只是单纯的查询或者删除操作的话,还是可以正常工作。
进行数据淘汰的策略
针对「进行数据淘汰」这一类策略,又可以细分为「在设置了过期时间的数据中进行淘汰」和「在所有数据范围内进行淘汰」这两类策略。在设置了过期时间的数据中进行淘汰:
- volatile-random:随机淘汰设置了过期时间的任意键值;
- volatile-ttl:优先淘汰更早过期的键值。
- volatile-lru(Redis3.0 之前,默认的内存淘汰策略):淘汰所有设置了过期时间的键值中,最久未使用的键值;
- volatile-lfu(Redis 4.0 后新增的内存淘汰策略):淘汰所有设置了过期时间的键值中,最少使用的键值;
在所有数据范围内进行淘汰:
- allkeys-random:随机淘汰任意键值;
- allkeys-lru:淘汰整个键值中最久未使用的键值;
- allkeys-lfu(Redis 4.0 后新增的内存淘汰策略):淘汰整个键值中最少使用的键值。
查看当前内存淘汰策略
可以使用 config get maxmemory-policy
命令,来查看当前 Redis 的内存淘汰策略,命令如下:
127.0.0.1:6379> config get maxmemory-policy
1) "maxmemory-policy"
2) "noeviction"
可以看出,当前 Redis 使用的是 noeviction
类型的内存淘汰策略,它是 Redis 3.0 之后默认使用的内存淘汰策略,表示当运行内存超过最大设置内存时,不淘汰任何数据,但新增操作会报错。
修改内存淘汰策略
设置内存淘汰策略有两种方法:
- 方式一:通过“
config set maxmemory-policy <策略>
”命令设置。它的优点是设置之后立即生效,不需要重启 Redis 服务,缺点是重启 Redis 之后,设置就会失效。 - 方式二:通过修改 Redis 配置文件修改,设置“
maxmemory-policy <策略>
”,它的优点是重启 Redis 服务后配置不会丢失,缺点是必须重启 Redis 服务,设置才能生效。
LRU和LFU算法的区别
在 Redis 中,LRU(Least Recently Used,最近最少使用)和 LFU(Least Frequently Used,最不经常使用)是两种常用的缓存淘汰算法,用于在内存不足时选择合适的键进行淘汰。
下面是 LRU 和 LFU 算法的区别:
-
LRU(最近最少使用)算法:
- 基本原理:根据键的访问时间来选择淘汰的键。当内存不足时,选择最长时间没有被访问的键进行淘汰。
- 实现方式:Redis 使用近似 LRU 算法(Approximated LRU),通过维护一个近似 LRU 的数据结构来追踪键的访问顺序。
- 优点:简单易懂,适用于访问模式较为均匀的场景。
- 缺点:不能准确地追踪访问频率,对于访问模式不均匀的场景可能会导致缓存命中率下降。
-
LFU(最不经常使用)算法:
- 基本原理:根据键的访问频率来选择淘汰的键。当内存不足时,选择访问频率最低的键进行淘汰。
- 实现方式:Redis 使用 LFU 算法来追踪键的访问频率。每个键维护一个访问计数器,每次访问加一,当内存不足时选择计数器最低的键进行淘汰。
- 优点:适用于访问模式不均匀的场景,能够较好地适应热点数据的访问需求。
- 缺点:实现复杂度较高,需要维护额外的数据结构来追踪访问频率。
需要注意的是,Redis 在实现 LRU 和 LFU 算法时,可能会使用不同的近似方法或优化策略,以平衡性能和准确性。在实际应用中,可以根据访问模式和缓存需求来选择合适的算法。有时也可以结合两种算法,根据具体的业务场景以及性能需求来进行淘汰策略的选择。
Redis如何实现LRU算法
第一种版本
Redis 使用近似 LRU(Least Recently Used,最近最少使用)算法来实现缓存淘汰策略。虽然不是严格的 LRU 算法,但它在实践中表现良好,并且具有较低的复杂度。Redis 中的 LRU 算法的实现基于以下原理:
- Redis 维护一个有序的 LRU 数据结构,通常是一个双向链表。
- 每个键都有一个额外的字段用于记录最近访问的时间戳或计数器。
- 当有新的键被访问时,Redis 将该键移动到链表的头部,表示最近被访问。
- 当需要淘汰键时,Redis 会从链表的尾部选择最久未被访问的键进行淘汰。
通过上述步骤,Redis 保持了键的访问顺序,并且可以从链表的尾部选择最久未被访问的键进行淘汰。这种近似 LRU 算法能够在大多数情况下有效地淘汰长时间未被访问的键,以腾出空间。
需要注意的是,Redis 的近似 LRU 算法并不是严格按照 LRU 的定义实现的。在实际中,为了减少复杂度和内存消耗,Redis 使用了一些近似算法和优化策略。例如,Redis 可能仅在特定的访问事件或条件下更新访问时间戳,或者限制链表的长度。这些优化可以提高性能和效率,但可能会导致略微的不准确性。
需要注意的是,Redis 的 LRU 算法是在内存中执行的,当内存不足时才会触发淘汰。如果需要持久化数据,可以考虑使用 Redis 的持久化机制(如 RDB 或 AOF)来保存数据,以防止数据丢失。
总体而言,Redis 的近似 LRU 算法在大多数情况下能够很好地满足缓存淘汰的需求,并提供了良好的性能和可靠性。
第二种版本
Redis 实现的是一种近似 LRU 算法,目的是为了更好的节约内存,它的实现方式是在 Redis 的对象结构体中添加一个额外的字段,用于记录此数据的最后一次访问时间。当 Redis 进行内存淘汰时,会使用随机采样的方式来淘汰数据,它是随机取 5 个值(此值可配置),然后淘汰最久没有使用的那个。Redis 实现的 LRU 算法的优点:
- 不用为所有的数据维护一个大链表,节省了空间占用;
- 不用在每次数据访问时都移动链表项,提升了缓存的性能;
但是 LRU 算法有一个问题,无法解决缓存污染问题,比如应用一次读取了大量的数据,而这些数据只会被读取这一次,那么这些数据会留存在 Redis 缓存中很长一段时间,造成缓存污染。
Redis如何实现LFU算法
Redis 在版本 4.0 及之后的版本中,引入了对 LFU(Least Frequently Used,最不经常使用)算法的支持,以实现缓存淘汰策略。Redis 中的 LFU 算法的实现基于以下原理:
- Redis 维护一个全局的 LFU 数据结构,用于存储所有键的访问频率信息。
- 对每个键,Redis 使用一个计数器来记录其访问频率。
- 每次访问一个键时,Redis 会将该键的计数器加一。
- 当需要淘汰键时,Redis 会选择访问频率最低的键进行淘汰。
通过上述步骤,Redis 能够追踪和更新每个键的访问频率,并选择访问频率最低的键进行淘汰。这种 LFU 算法能够在缓存中保留经常被访问的热点数据,以提高缓存命中率。
需要注意的是,Redis 的 LFU 算法是一个近似算法,实际上它使用了一个叫做 “LFU-Tiny” 的实现。这种实现方式通过将计数器分为多个时间窗口,以降低计数器的复杂度和内存消耗。在使用 LFU 算法时,可以通过以下命令来启用 LFU 算法的淘汰策略:
CONFIG SET maxmemory-policy LFU
这将设置 Redis 的最大内存策略为 LFU。需要注意的是,由于 LFU 算法需要维护频率计数器和全局数据结构,可能会导致一些额外的性能开销。因此,在选择使用 LFU 算法时,需要根据实际场景和需求权衡性能和准确性。总体而言,Redis 的 LFU 算法在大多数场景下能够提供良好的缓存淘汰效果,并提高缓存命中率。