文章目录
- Redis内存淘汰详解
- 0. 前言
- 大纲
- Redis内存淘汰策略
- 1. 什么是Redis内存淘汰策略?
- 1.1.Redis 内存不足的迹象
- 2. Redis内存淘汰策略
- 3. 原理
- 4. 主动和被动
- 1. 主动淘汰
- 1.1 键的生存周期
- 1.2 过期键删除策略
- 2. 被动淘汰
- 2.2 被动淘汰策略的实现
- 5. 项目实践优化策略
- 6. Redis 服务配置淘汰策略
- 5.参考资料
Redis内存淘汰详解
0. 前言
大家有没有想过 对于内存型中间件Redis 来说。当 Redis 的内存达到最大限制时,会发生什么情况?redis 会怎么处理。由此我们引发了如下 的问题。
当 Redis 的内存达到最大限制时,会发生什么情况?
Redis 为什么设计了内存淘汰策略
Redis 是如何选择要淘汰的键的?
什么是内存淘汰策略?有哪些常见的内存淘汰策略?
如何配置 Redis 的内存淘汰策略?
不同的业务场景适合哪种内存淘汰策略?
Redis 内存淘汰策略的选择对系统性能有什么影响?
通过本文我们详细了解一下Redis内存淘汰详解
大纲
Redis内存淘汰策略
Noeviction:当内存不足时,禁止所有写操作,返回错误。
Allkeys-LRU:最近最少使用(LRU)算法。当内存不足时,从已设置过期时间的key中,移除最近最不经常使用的。
Allkeys-RANDOM:随机算法。当内存不足时,从已设置过期时间的key中,随机移除某个key。
Volatile-LRU:最近最少使用(LRU)算法。当内存不足时,从已设置过期时间的key中,移除最近最不经常使用的。
Volatile-RANDOM:随机算法。当内存不足时,从已设置过期时间的key中,随机移除某个key。
Volatile-TTL:过期时间(TTL)算法。当内存不足时,从已设置过期时间的key中,移除即将过期的key。
1. 什么是Redis内存淘汰策略?
Redis 内存淘汰策略是在 Redis 内存空间不足时,根据一定的规则和算法来决定清除哪些键以释放内存空间的策略。由于 Redis 是基于内存的键值存储系统,当内存容量达到上限时,无法再存储新的数据。为了解决这个问题,Redis 提供了多种内存淘汰策略,用于决定哪些键被优先清除,以便为新的数据腾出空间。
不同的内存淘汰策略有不同的原理和行为。以下是几种常见的 Redis 内存淘汰策略:
-
最近最少使用(Least Recently Used, LRU):LRU 策略会根据键的最近使用时间来判断其热度。当内存空间不足时,会优先淘汰最久未使用的键,即最近最少被使用的键。
-
先进先出(First In, First Out, FIFO):FIFO 策略按照键的插入顺序来决定淘汰顺序。最先插入的键会被最先淘汰。
-
最少使用(Least Frequently Used, LFU):LFU 策略根据键被访问的频率来决定淘汰顺序。访问次数较少的键会被优先淘汰。
-
随机(Random):随机策略会随机选择要淘汰的键。
选择适合的内存淘汰策略取决于应用的需求和数据访问模式。某些策略可能更适合于缓存场景,而另一些策略可能更适合于持久化数据场景。可以通过配置文件或动态命令来设置 Redis 的内存淘汰策略。
1.1.Redis 内存不足的迹象
当 Redis 发生内存不足时,程序可能会报错并给出相应的错误信息。常见的错误信息可能包括:
-
“OOM command not allowed when used memory”:这个错误表示 Redis 的内存已经耗尽,无法执行当前命令。
-
“Redis out of memory”:这个错误表示 Redis 已经耗尽了可用的内存,无法继续存储更多的数据。
-
“Max number of clients reached”:这个错误表示 Redis 已经达到最大连接数限制,无法接受更多的客户端连接。
当 Redis 的内存不足时,可能会出现以下现象: -
写入失败:当 Redis 内存不足以容纳新的数据时,写入操作会失败。Redis 可能会拒绝接受写入请求,并返回相应的错误信息,如 “OOM command not allowed when used memory”。
-
读取失败:当 Redis 内存不足时,读取操作也可能失败。Redis 可能无法提供请求的数据,并返回相应的错误信息。
-
命令执行缓慢:当 Redis 的内存接近饱和状态时,性能可能会下降。由于 Redis 需要进行内存淘汰操作来释放空间,这可能会导致命令执行的延迟增加。
-
内存淘汰:为了腾出内存空间,Redis 会根据配置的内存淘汰策略清除一些键。这可能导致一些数据被删除,从而影响应用程序的行为。
-
连接失败:当 Redis 内存不足以处理更多的客户端连接时,新的连接请求可能会被拒绝或延迟处理。这可能导致连接失败或长时间等待。
这些现象可能会对应用程序的正常运行产生影响,导致数据丢失、延迟增加或服务不可用。为了避免内存不足的问题
2. Redis内存淘汰策略
Redis内存淘汰策略主要是针对Redis实例达到最大内存限制后,如何处理新进来的数据的策略。当Redis已用内存超过maxmemory限定的值,就会触发淘汰策略。以下是Redis支持的各种淘汰策略:
-
noeviction
:默认策略,当内存使用达到阈值的时候,如果继续执行写入,Redis会返回错误信息,读操作不会受影响。 -
allkeys-lru
:移除最少最近使用的key,这是最常用的一种策略,对内存管控较为精确。 -
volatile-lru
:在设置了过期时间的key中,移除最少最近使用的key。 -
allkeys-random
:随机移除某个key,这种策略对内存的控制较为粗糙。 -
volatile-random
:在设置了过期时间的key中,随机移除某个key。 -
volatile-ttl
:在设置了过期时间的key中,有更早过期时间的key会被优先移除。
选择哪种淘汰策略,取决于你的业务特性和对数据的要求。比如,如果你的数据大部分都设置了过期时间,那么volatile-*
策略可能更适合你。如果你的数据是重要性逐渐降低的类型,那么*lru
策略可能更适合你。但是一般来说,allkeys-lru
和volatile-lru
是使用最广泛的策略。
另外,对于Redis的内存管理,除了淘汰策略外,还有一些其他的优化手段,比如定时删除、延迟删除、内存碎片整理等。这些手段对于减少内存使用,提高Redis性能都有一定的帮助。
3. 原理
在Redis的设计中,内存淘汰的实现原理与其数据结构和管理机制紧密关联。具体来说,不同的内存淘汰策略有不同的实现方式。 一般来说,volatile-*
策略适合大部分键值对都设置了过期时间的场景,而*lru
策略则适合数据的重要性随时间逐渐降低的场景。当然,这些策略都不是银弹,可能需要根据实际情况进行调整和优化。
首先,当我们开启Redis的maxmemory策略并设置了相应的内存上限后,Redis就会在达到这个上限时触发数据淘汰操作。这个操作是如何进行的呢?它主要依赖于Redis的内存淘汰策略。
我们以
allkeys-lru
策略为例,这个策略的原理是什么呢?LRU代表Least Recently Used,即最近最少使用。Redis为每个存储在内存中的key都维护了一个LRU标识,这个标识用于记录该key最后一次被访问的时间。当Redis需要淘汰数据时,它会从所有的key中挑选一部分key,检查它们的LRU标识,然后淘汰其中最少被访问的key。
这就好比一个图书馆,当图书馆的空间不足时,图书管理员会选择出借阅率最低的书进行淘汰,以便为新书腾出空间。
同理,
volatile-lru
策略也是基于LRU原理,只不过它只会考虑那些设置了过期时间的key。
allkeys-random
和volatile-random
策略则是在所有key或者设置了过期时间的key中随机选择一个进行淘汰,就像是“闭眼摸象”。
volatile-ttl
策略则是在设置了过期时间的key中找出生存时间(TTL)最短的进行淘汰,也就是“谁生命短,谁先走”。
-
noeviction
:实现原理十分简单,就是当内存达到上限时,对于所有的写操作都返回错误。 -
allkeys-lru
:它的实现基于Redis的数据结构和LRU(Least Recently Used,最近最少使用)策略。在Redis中,每个键值对都有一个LRU字段,表示这个键值对上一次被访问的时间。当内存达到上限时,Redis会观察一部分键值对的LRU字段,选择其中LRU值最小(即最久未被访问)的键值对进行淘汰。 -
volatile-lru
:与allkeys-lru
类似,只不过它只对设置了过期时间的键值对进行观察和淘汰。 -
allkeys-random
:每当需要淘汰数据时,Redis随机选取一部分键值对,然后从中随机淘汰一个。这种方式的优点是简单且开销较小,缺点是淘汰的数据可能是重要的数据。 -
volatile-random
:与allkeys-random
类似,只是它只对设置了过期时间的键值对进行随机淘汰。 -
volatile-ttl
:这个策略的实现也依赖Redis的数据结构。每个设置了过期时间的键值对都有一个ttl(time to live,生存时间)字段。当内存达到上限时,Redis会观察一部分设置了过期时间的键值对的ttl字段,选择其中ttl值最小(即最快过期)的键值对进行淘汰。
Redis中的LRU算法并没有严格按照常规的LRU算法的方式实现,而是基于LRU算法的思想做了自己的优化。我们知道,实现LRU算法时,需要将所有的数据按照访问时间距离当前时间的长短排序放到一个双向链表中,基于这个链表实现数据的淘汰。但Redis中存储的数据量是非常庞大的,如果要基于常规的LRU算法,就需要把所有的key全部放到这个双向链表中,这样就会导致这个链表非常非常大,不止需要提供更多的内存来存放这个链表结构,而且操作这么庞大的链表的性能也是比较差的。
所以,Redis中的LRU算法是这样实现的:首先定义一个淘汰池,这个淘汰池是一个数组(大小为16),然后触发淘汰时会根据配置的淘汰策略,先从符合条件的key中随机采样选出5(可在配置文件中配置)个key,然后将这5个key按照空闲时间排序后放到淘汰池中,每次采样之后更新这个淘汰池,让这个淘汰池里保留的总是那些随机采样出的key中空闲时间最长的那部分key。需要删除key时,只需将淘汰池中空闲时间最长的key删掉即可。
为了方便理解它的思想放上一张图:
4. 主动和被动
1. 主动淘汰
主动淘汰是指 Redis 定期主动查找并删除已经过期的键(key)。Redis 使用了一种称为惰性删除(lazy deletion)的策略来处理过期键。具体来说,当访问一个键时,Redis 会先检查该键是否已过期,如果过期则将其删除。主动淘汰(定期删除)是 Redis 内存管理的一种重要机制,用于定期查找并删除已过期的键,以释放内存空间。它补充了 Redis 的惰性删除策略,确保过期键被及时清除。
然而,这种惰性删除策略可能会导致过期键在被访问之前一直保留在内存中。为了解决这个问题,Redis 引入了主动淘汰机制,也称为定期删除(eviction)。主动淘汰是指 Redis 在一定时间间隔内定期地查找并删除已经过期的键,以释放内存空间。
Redis 使用了一种叫作定期删除的算法来实现主动淘汰。该算法会在每个 Redis 数据库的主线程中以一定的频率运行。它会随机抽样一部分键,检查它们是否过期,如果过期则删除。通过定期删除,Redis 可以及时释放过期键占用的内存,保持内存使用的效率。
定期删除的频率可以通过配置参数 hz
(每秒运行的周期数)来调整,默认为 10 次每秒。可以根据实际需求和系统负载来调整这个值,以平衡定期删除的开销和内存空间的回收效率
\定期删除只能清除已经过期的键,对于没有设置过期时间的键,定期删除不会进行清除。对于没有过期时间的键,仍然需要依靠惰性删除策略在访问时判断是否过期并删除。
1.1 键的生存周期
我们可以使用EXPIRE命令为key设置生存周期。一旦设置了生存周期,到了过期时间,这个key就会被认为是过期的。
1.2 过期键删除策略
Redis采用一个定时任务,每秒检查一定数量的key,删除其中已经过期的key。这样做的好处是可以均摊删除key带来的计算和I/O压力,避免在某一时刻删除大量key导致系统卡顿。
2. 被动淘汰
被动淘汰是指当客户端尝试访问一个已经过期的键(key)时,Redis 会在访问时将该键删除,并返回一个键不存在的错误。
在 Redis 中,被动淘汰是一种基于访问的过期删除策略。当客户端尝试读取或写入一个键时,Redis 会检查该键是否已经过期。如果键已经过期,Redis 会立即将其删除,并返回一个键不存在的错误给客户端。
这种被动淘汰策略确保了过期键在被访问时被删除,避免了过期键一直占据内存空间的问题。它是与 Redis 的惰性删除策略相结合的一种机制,以实现对过期键的自动清除。
被动淘汰只适用于在访问时才会触发删除的场景。对于没有被访问的过期键,仍然需要依靠惰性删除和定期删除来清除。同时,被动淘汰也仅适用于单个键的删除,而不会触发对其他键的删除操作。
被动淘汰的优点是能够实时删除过期键,避免了过期键长时间占用内存的问题。然而,它也可能导致在高并发环境下频繁触发删除操作,对性能产生一定的影响。因此,在设计 Redis 数据模型时,需要综合考虑过期时间的设置和访问模式,选择合适的过期删除策略以及适当的内存淘汰策略,以满足应用的需求。
2.2 被动淘汰策略的实现
当客户端访问一个key时,Redis会首先查看这个key的过期时间。如果当前时间已经超过了这个过期时间,那么Redis就会删除这个key,然后返回一个错误,告诉客户端这个key不存在。
这两种策略的选择取决于具体的使用场景。一般来说,如果你的系统中有大量的过期key,那么可能主动淘汰策略会更有效。反之,如果过期key比较少,那么被动淘汰策略可能会更合适。
就像一个图书馆,如果图书馆的书架上有很多过期的书,那么图书管理员可能需要定期去清理这些书。而如果过期的书比较少,那么图书管理员可能只需要在读者找这本书的时候告诉他这本书已经过期,然后进行清理工作。
5. 项目实践优化策略
-
定时删除(TTL):可以为 Redis 中的键设置过期时间(Time To Live,TTL),让键在一定时间后自动过期并被删除。这样可以确保过期的键及时被清除,释放内存空间。
-
延迟删除(Lazy Expire):延迟删除是一种优化手段,当键过期时并不立即删除,而是在访问该键时才进行删除操作。这可以减少删除操作的开销,并在实际需要时进行键的清除。
-
内存碎片整理(Memory Defragmentation):在 Redis 中,当删除一些键后,可能会导致内存出现碎片化。这些碎片化的内存空间无法被有效利用。内存碎片整理是一种优化手段,通过重新分配内存来整理碎片化的内存空间,以提高内存的利用率。
-
压缩存储(Memory Compression):Redis 4.0 版本引入了内存压缩(Memory Compression)功能,使用 LRU 算法对键进行压缩,以减少内存使用。压缩存储可以在某些场景下显著降低内存占用。
-
内存分配器优化:Redis 使用的内存分配器可以影响其性能和内存管理。选择适合的内存分配器,如 jemalloc,可以提高 Redis 的性能和内存利用率。
6. Redis 服务配置淘汰策略
当你配置 Redis 的淘汰策略时,你可以通过编辑 Redis 配置文件来实现。
。可以根据需要将 maxmemory-policy
设置为你选择的淘汰策略,例如 allkeys-lru
、volatile-lfu
等。同时,你还可以使用 maxmemory
配置项来设置 Redis 的最大内存限制。rdbcompression
用于启用在内存中存储数据时的压缩,它减少了数据占用的内存空间。其次,rdbcompression
用于在进行 RDB 快照持久化时启用压缩,以减小生成的 RDB 文件的大小。
# Redis 配置文件# ...################################## MEMORY MANAGEMENT ###################################### 当达到最大内存限制时,Redis 的淘汰策略
# 可选值:noeviction(禁止淘汰), allkeys-lru(LRU算法), allkeys-lfu(LFU算法), allkeys-random(随机淘汰),
# volatile-lru(LRU算法,仅针对设置了过期时间的键), volatile-lfu(LFU算法,仅针对设置了过期时间的键),
# volatile-random(随机淘汰,仅针对设置了过期时间的键)
# 默认值:noeviction
maxmemory-policy noeviction# Redis 的最大内存限制(以字节为单位)。
# 当达到最大内存限制时,Redis 将根据淘汰策略进行键的淘汰。
# 默认值:0(无限制)
maxmemory 102400# 启用压缩存储
# 开启压缩存储可以减少存储数据所占用的内存空间,但会增加 CPU 负载。
# 默认值:no
rdbcompression yes# 保存 RDB 文件时启用压缩
# 当进行 RDB 快照持久化时,启用压缩可以减少 RDB 文件的大小。
# 默认值:no
rdbcompression yes# ...##########################################################################################
编辑完成后,保存并重新启动 Redis 服务器,以使新的配置生效。
5.参考资料
- Redis官方文档:https://redis.io/
- Redis实战(书籍)
- Redis设计与实现(书籍)
- https://www.toutiao.com/article/7088132873051488780/