1.过期策略

1.1 定期删除

Redis中所有设置过期时间的KEY都会被记录到一个字典中,通过定时任务每隔一段时间(默认100ms)对字典中的KEY进行扫描删除过期数据,当然这种定时扫描只会发生在master节点上,slave节点会通过主节点同步的DEL命令进行同步达到删除过期键的目的。

扫描过程中如果过期KEY在总数中占的比例少于25%,则终止循环,这么做的目的是为了节省CPU资源,为了防止过期KEY数量过多导致循环一直执行下去,Redis增加了超时限制,消耗一定时间循环也会被打破。

1.2 惰性删除

Redis在执行任何读写命令时都会先找到这个KEY,惰性删除就作为一个切入点在命令执行前判断KEY是否过期,如果过期则进行删除。这种机制主要为了弥补定期删除策略扫描无法覆盖全部KEY的情况。

2.淘汰策略

2.1 淘汰规则

Redis的内存淘汰机制是针对内存不足的情况下,采用某种策略淘汰掉内存中的部分数据,保证服务可以继续写入数据,Redis提供了六种淘汰规则:

  • noeviction: 禁止淘汰,当内存达到阈值时,后续所有需要申请内存的命令都会报错
  • allkeys-random: 从数据集中任意选择数据淘汰
  • allkeys-lru: 从数据集中挑选最近最少使用的数据淘汰
  • volatile-lru: 从设置了过期时间的数据集中挑选最近最少使用的数据淘汰
  • volatile-ttl: 从已设置了过期时间的数据集中挑选即将要过期的数据淘汰
  • volatile-random: 从已设置了过期时间的数据集中任意选择数据淘汰

如果内存中不存在设置过期时间的key,会导致前缀为volatile的三种策略找不到任何可以清理的key,结果和noeviction一样报错。

2.2 文件配置

redis.conf
# 策略启动阈值(单位字节,64位系统中0表示不限制,32位系统中隐式不能超过3GB)
maxmemory 0

# 淘汰策略类型
maxmemory-policy noeviction

# 样本池数量
maxmemory-samples 5

2.3 淘汰过程

当Redis服务内存达到maxmemory后,对于所有的读写请求,都会触发freeMemoryIfNeeded函数以清理内存,并且这个清理过程是阻塞的,直到腾出足够的内存空间。整个清理过程并不是针对所有key,而是随机抽取maxmemory-samples个样本key,根据设置好的淘汰策略从样本范围中进行淘汰。

maxmemory-samples的大小对于noeviction和random策略没有任何影响,对于lru和ttl策略来说,值的大小直接影响到淘汰的精准度。值越大则样本范围越大,精准度越高,缺点是CPU耗时也会越高,在清理函数阻塞情况下甚至造成请求卡顿,反之亦然。

2.4 lru算法实现

lru算法是淘汰掉最近最少使用的数据,由于Redis采用样本随机抽取的机制清理数据,因此并不是严格意义上的lru算法。Redis给每个key额外添加了一个25bit的字段,存储最后一次的访问时间,并以此字段作为参考值实现lru算法。

在3.0版本进行优化,维护一个大小为16的候选池,池中的数据根据访问时间排序,第一次抽取的样本都会放入池中,后续随机抽取的样本数据只有在访问时间小于池中最小时间的情况下,才会放入池中,直到候选池放满数据。再往后抽取的样本中,如果仍然存在访问时间小于池中的数据,将池中访问时间最大的移除并添加到池中。当需要淘汰数据时,直接从池中选取最久没被访问的key淘汰掉。

3.RDB持久化

RDB即快照,是Redis默认的持久化方式,对某个特定时间点的数据进行一次全量备份,并将备份结果写入一个紧凑的二进制文件中,除此之外Redis还支持命令的方式手动触发一次RDB持久化。

3.1手动触发

SAVE: 同步持久化,由于Redis所有命令仅用一个线程进行处理,因此整个持久化过程会阻塞所有读写请求,如果数据量大的话会造成长时间的阻塞,生产环境一般禁止使用。

BGSAVE: 异步持久化,Redis接收到此命令后会fork出一个子进程处理持久化工作,由于Redis所有命令仅用一个线程进行处理,因此fork期间所有读写请求仍然是阻塞的,直到fork完毕后继续处理请求,与此同时,子进程异步执行持久化工作。

3.2 自动触发

在配置文件redis.conf中,默认设置了三种自动触发的规则,用户可以根据自身业务场景修改触发规则。如果同时配置多个save选项,那么只要其中任一条满足,Redis都会触发一次BGSAVE操作:

1
2
3
4
5
6
7
8
9
# 自动触发的规则格式:
# save <seconds> <changes>

# 900秒(15分钟)内至少发生一次写操作,触发RDB持久化
save 900 1
# 300秒(5分钟)内至少发生10次写操作,触发RDB持久化
save 300 10
# 60秒(1分钟)内至少发生10000次写操作,触发RDB持久化
save 60 10000

当手动执行shutdown命令关闭服务器时,如果没有开启AOF持久化功能,也会自动触发一次bgsave命令进行RDB持久化。

3.3 执行流程

  • 执行bgsave命令的时候,Redis主进程会检查是否有子进程在执行RDB/AOF持久化任务,如果有的话,直接返回

  • Redis主进程会fork一个子进程来执行执行RDB操作,fork操作会对主进程的读写请求造成阻塞,fork操作完成后会发消息给主进程,从而不再阻塞主进程。

  • RDB子进程会根据Redis主进程的内存生成临时的快照文件,持久化完成后会使用临时快照文件替换掉原来的RDB文件。该过程中主进程的读写不受影响,但Redis的写操作不会同步到主进程的主内存中,而是会写到一个临时的内存区域作为一个副本。

  • 子进程完成RDB持久化后会发消息给主进程,通知RDB持久化完成,并将上阶段内存副本中的增量写数据同步到主内存。

3.4 优缺点

优点是主进程在整个持久化过程中唯一要做的就是fork出子进程,剩下的持久化工作全部由子进程完成,父进程无需执行任何磁盘IO操作。RDB文件是紧凑的二进制数据构成,占用磁盘相对AOF较小,在服务重启加载数据时速度会快很多。

缺点是无法做到实时持久化,一旦服务器异常退出或宕机,会导致最后一次快照后面的所有数据丢失,因此需要根据自身场景配置触发规则,尽量减少这种意外带来的损失。另外主进程fork子进程属于重量级操作,过程中会阻塞主进程的读写请求,因此配置触发规则除了考虑到减少意外带来的损失以外,还要考虑到频繁RDB对读写请求响应时间带来的影响。

4.AOF持久化

AOF采用命令日志的方式进行内存数据的持久化,每条写命令都会记录到AOF日志中,当Redis需要重启时会逐条执行命令日志,将持久化数据恢复到内存中。

4.1 开启方式

AOF持久化方式默认是关闭的,可以通过配置文件redis.conf进行开启,如果RDB和AOF同时开启,Redis在启动时默认加载AOF文件恢复数据,并且后续采用AOF方式进行持久化。

redis.conf中,关于AOF的相关配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 此选项为aof功能的开关,默认为no,可以通过修改为yes来开启aof功能
appendonly yes

# 指定aof文件名称
appendfilename appendonly.aof

# 指定aof操作中文件同步策略,分为always、everysec(默认)、no三种
appendfsync everysec

# 在aof-rewrite期间,appendfsync是否暂缓文件同步
no-appendfsync-on-rewrite no

# aof文件rewrite触发的最小文件尺寸(mb,gb),只有大于此aof文件大于此尺寸是才会触发rewrite,默认64mb
auto-aof-rewrite-min-size 64mb

# 相对于“上一次”rewrite,本次rewrite触发时aof文件应该增长的百分比
auto-aof-rewrite-percentage 100

4.2 appendfsync

AOF对于每条写命令都会追加到文件中,当写命令过于频繁会导致磁盘IO的负荷加重,另外Linux系统提供了pageCache机制将部分磁盘IO请求转化为内存IO,并通过异步方式刷回磁盘文件,这种机制在一定程度上缓解了AOF文件写入的压力,同时也增加了数据丢失的可能,比如数据写入内核缓存后还没来得及刷回磁盘发生断电,那么这部分数据就会丢失。

Redis提供了三种刷盘的策略:

  • always: 每条写命令都会调用操作系统的fsync函数,强制从内核缓存刷回磁盘,较为安全,代价是性能很低。
  • everysec: 每秒调用一次操作系统的fsync函数,是一种折中的处理方式,最多丢失一秒的数据。
  • no: Redis永远不主动调用fsync函数,让操作系统自身选择何时同步磁盘,性能较好,缺点是断电情况下会造成更多的丢失可能。

4.3 rewrite(重写机制)

AOF采用记录命令的方式持久化,这会导致随着时间的推移AOF文件会越来越大,因此需要定期对AOF日志进行压缩,压缩的过程就是rewrite。例如对key1初始值是0,共调用incr命100次,key1的值变为100,那么一条set key1 100 就可以合并之前的100条命令。

rewrite目的是对AOF文件的压缩,但是执行过程中并不是对现有AOF文件进行编辑删减,而是采取类似RDB快照的方式,遍历内存中所有数据,逐个将数据对应的命令添加到新的AOF文件中。在rewrite过程中对于新的变更操作仍然写入到旧的AOF文件中,不过这些命令Redis会单独在保存一份,当内存中的数据全部写入到新的AOF文件后,之前单独存储的新命令也会一并写入到新AOF文件,最后替换掉旧的AOF文件。

4.4 no-appendfsync-on-rewrite

在对AOF文件压缩期间,新的写命令仍然会记录到旧的AOF文件中,在假设新的AOF文件必定生成成功的前提下,这部分命令是否成功写入旧的AOF文件已经不重要了,这段时间调用操作系统的fsync函数强制刷盘完全是资源浪费,可以暂缓。但是这个参数Redis给的默认值是no,也就是说Redis不推荐暂缓强制刷盘,可能是出于安全考虑吧。

4.5 自动触发rewrite

auto-aof-rewrite-min-size是自动触发rewrite的最小文件尺寸,默认64mb。当某条写命令追加到AOF文件后文件大于64mb,会自动触发rewrite。

auto-aof-rewrite-percentage是自动触发rewrite的最小增长比例,默认100%。每次rewrite后都会记录此刻AOF文件的大小,后续每次追加命令都会获取当前AOF文件大小,和最初的大小比较并计算增长百分比,当增长比例超过100%触发rewrite。

注:只有俩个条件都满足的情况下才会触发rewrite。

4.6 手动触发rewrite

如果你的Redis一直使用RDB持久化方式,并且想更换为AOF,那么可以使用config命令动态修改redis.conf配置,然后发送此命令将扫描当前内存数据,生成一份AOF文件。

触发命令:
redis-cli -h ip -p port bgrewriteaof

4.7 优缺点

优点是大部分情况下仅仅是对AOF文件追加写日志,对服务器性能影响较小,并且在默认配置下最多丢失一秒的数据。AOF文件的内容都是命令,可以兼容任何Redis版本。

缺点是在数据量较大的情况下,会不断触发rewrite,对AOF文件进行压缩。即使经过压缩,由于自身是文本文件,体积相对RDB(二进制文件)要大得多。并且在服务重启过程中需要重演命令式的恢复数据,相对于RDB要慢上许多。

4.8 AOF文件修复

在将命令写入AOF文件过程中,可能因为宕机造成文件有错误,导致Redis服务重启时拒绝加载此AOF文件,可以通过redis-check-aof工具修正AOF文件。

5.混合持久化

5.1 开启方式

1
2
# yes开启 no关闭
aof-use-rdb-preamble yes

5.2 执行过程

RDB与AOF俩种持久化方式的优缺点都很明显,因此Redis4.0推出了混合持久化方式,将原AOF文件内容变成了前部分存储RDB格式数据、后部分存储AOF格式数据,既保证了服务运行期间数据不丢失,又避免了服务重启数据恢复慢的问题:
图片

评论