Redis内存回收和持久化策略

it2024-12-30  13

概述

Redis所有数据都存在内存中,在某些时候需要对占用的内存空间进行回收,内存回收主要分为两类,一类是key过期,一类是内存使用达到上限,触发内存淘汰。

Key过期策略

要实现key过期,有几种思路:

定时淘汰(主动淘汰)

每个设置了过期时间的key都会设置一个定时器,定时时间为key的过期时间,然后key过期了,定时器也正好触发,就会立即清除过期的那个key,该策略可以立即清除过期的数据,对内存很友好,但是因为创建了大量定时器,会占用大量的CPU,从而影响缓存的响应时间和吞吐量。

惰性淘汰

只有当访问一个key时,才会判断key是否过期,过期就清除,该策略可以最大化节省cpu资源,但是对内存很不友好,key不访问就算过期了也得不到清除,极端情况下大量过期key得不到访问从而导致大量过期key得不到清除。

定期淘汰

每隔一定的时间(定期),会扫描一定数量的数据库的 expires 字典中一定数量的 key,并清除其中已过期的 key(不是全部扫描全部清除,而是扫描一部分,并清除其中的过期key)。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得 CPU 和内存资源达到最优的平衡效果。

expires 字典是记录了设置了过期时间的key的指针。

Redis默认开启了定期淘汰和惰性淘汰策略,这两个策略相互配合可以很好的解决过期key的处理,一般不要开启定时淘汰策略。

淘汰策略

如果清理了过期key,但是内存还是满了,就要使用内存的淘汰算法来决定清理哪些数据。

redis服务器的内存配置redis.conf:

maxmemory <bytes> #配置redis能使用的最大内存,单位byte,如果不设置,64 位系统不限制内存,32 位系统最多使用 3GB 内存。

redis5.0有8种内存淘汰策略:

淘汰策略解释volatile-lru根据LRU(最近最少使用)算法来删除设置了过期时间的key(只针对设置了过期时间的key,持久化key不会被淘汰),直到腾出足够内存为止,如果没有可以删除的key,也就是删除完了能删除的所有key后,还没有腾出足够内存的话,就会回退到noeviction策略allkeys-lru跟上面的淘汰算法一样,只是他针对的是所有key,会一直删除key,直到腾出足够内存volatile-lfu淘汰访问频率最少的key,redis内部会对每个key维护一段时间内的访问(频率) ,这里只针对设置了过期时间的keyallkeys-lfu跟上面的淘汰算法一样,只是他针对的是所有key,会一直删除key,直到腾出足够内存volatile-random使用随机算法,在设置了过期时间的key集合中随机淘汰keyallkeys-random使用随机算法,针对所有key,会一直删除key,直到腾出足够内存volatile-ttl删除离过期时间最近的key,也就是删除最快过期的key,直到腾出足够内存,如果没有, 回退到 noeviction 策略noeviction默认策略, 不会删除任何数据, 拒绝所有写入操作并返回客户端错误信息(error) OOM command not allowed when used memory, 此时 Redis 只响应读操作。

如果没有符合前提条件的 key 被淘汰,那么 volatile-lru、volatile-lfu、volatile-random 、volatile-ttl 相当于 noeviction(不做内存回收)。

在配置文件中通过maxmemory-policy volatile-lru 设置指定的淘汰策略,建议使用 volatile-lru,在保证正常服务的情况下,优先删除最近最少使用的 key。

频率是是用基于概率的对数计数器实现的。 关于LFU算法的两个配置: lfu-log-factor 10 这个是设置频率增长的速率,设置的值越大,频率增长的越慢。 lfu-decay-time 1 这个是设置频率衰减的,如果设置为1,那么就是一分钟没有被访问,就衰减1。

持久化

Redis 速度快,很大一部分原因是因为它所有的数据都存储在内存中。如果断电或者宕机,都会导致内存中的数据丢失。为了实现重启后数据不丢失,Redis 提供了两种持久化的方案,一种是 RDB 快照(Redis DataBase),一种是 AOF(Append Only File)。

持久化只会持久化没有设置过期时间的key,设置了过期时间的key会自动忽略。

RDB

RDB 是 Redis 默认的持久化方案。当满足一定条件的时候,会把当前内存中的数据写入磁盘,生成一个快照文件 dump.rdb。Redis 重启会通过加载 dump.rdb 文件恢复数据。

触发条件: 自动触发: 通过配置redis.conf文件来设置自动触发RDB的规则,其中定义了触发把数据保存到磁盘的触发频率。 如果不需要 RDB 方案,注释 save 或者配置成空字符串""。

#默认如下三个规则,如果符合下面的任意规则,redis服务就会进行一个数据快照。 save 900 1 #900秒内对至少一个key进行修改、添加、删除等事务操作。 save 300 10 #300秒内对至少10个key进行修改、添加、删除等事务操作。 save 60 10000 #60秒内对至少10000个key进行修改、添加、删除等事务操作。 dir 文件目录 #设置生成的RDB文件存放的目录。默认在启动目录下(相对路径),同时启动redis服务器恢复数据时也是使用该目录下的RDB文件。 dbfilename 文件名 #生成的RDB文件的文件名。同时也是用于恢复数据的RDB文件。 rdbcompression yes #是否对生成的RDB文件进行压缩,开启压缩可以节省存储空间, 但是会消耗一些 CPU 的计算时间, 默认开启 rdbchecksum yes #使用 CRC64 算法来进行数据校验, 但是这样做会增加大约 10%的性能消耗, 如果希望获取到最大的性能提升, 可以关闭此功能。

shutdown 触发,保证服务器正常关闭,在服务器正常关闭的时候会触发一次快照。 flushall 命令会触发,但是生成的RDB 文件是空的,没什么意义(删掉 dump.rdb 演示一下)。

手动触发: 可以手动触发RDB快照,为了迁移数据,备份数据,或者定时RDB保证数据尽可能少丢失等原因时,需要手动触发RDB快照。

a)save命令 save 在生成快照的时候会阻塞当前 Redis 服务器, Redis 不能处理其他命令。如果内存中的数据比较多,会造成 Redis 长时间的阻塞。生产环境不建议使用这个命令。

b)bgsave命令 执行 bgsave 时,Redis 会在后台异步进行快照操作,快照同时还可以响应客户端请求。 具体操作是 Redis 进程执行 fork 操作创建子进程(copy-on-write),RDB 持久化过程由子进程负责,完成后自动结束。它不会记录 fork 之后后续的命令。阻塞只发生在fork 阶段,一般时间很短。

用 lastsave 命令可以查看最近一次成功生成快照的时间。

优势:

RDB 是一个非常紧凑(compact)的文件,它保存了 redis 在某个时间点上的数据集。这种文件非常适合用于进行备份和灾难恢复。生成 RDB 文件的时候,redis 主进程会 fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘 IO 操作。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

劣势:

RDB数据没办法做到实时持久化(秒级持久化或者命令及持久化)因为 bgsave 每次运行都要执行 fork 操作创建子进程,频繁执行成本过高。在一定间隔时间做一次备份,所以如果 redis 意外 down 掉的话,就会丢失最后一次快照之后的所有修改(数据有丢失)。如果数据相对来说比较重要,希望将损失降到最小,则可以使用 AOF 方式进行持久化。
AOF持久化

AOF:Redis 默认不开启。AOF 采用日志的形式来记录每个写操作,并追加到文件中,开启后,执行了更改redis服务数据的操作命令时,就会把命令写入到AOF文件中。然后如果要使用AOF文件来恢复数据的话,redis在启动时,会把AOF文件中的所有命令执行一遍,数据就恢复了。

appendonly no #是否开启aof持久化,默认no不开启。 appendfilename "appendonly.aof" #生成的aof文件名,目录使用dir来设置。

由于操作系统的缓存机制,AOF数据并没有真正地实时写入到磁盘中,而是进入了系统的硬盘缓存中。什么时候真正写到磁盘要由持久化策略决定。

AOF持久化策略:

#fsync操作表示把数据从硬盘缓存真正写入到磁盘中。 appendfsync everysec #默认everysec 表示每秒执行一次 fsync, 可能会导致丢失这 1s 数据。 通常选择 everysec ,兼顾安全性和效率。 #no 表示不执行 fsync, 由操作系统自行保证数据啥时候同步到磁盘, 速度最快,但是不太安全;因为没有刷到磁盘期间服务器宕机了,就会丢失这段时间的数据,因为没有真正写入到磁盘。 # always 表示每次写入都执行 fsync, 以保证数据同步到磁盘, 效率很低;但是最多会丢失一个命令的数据。

fsync会阻塞主进程。

AOF重写: 由于 AOF 持久化是 Redis 不断将写命令记录到 AOF 文件中,随着 Redis 不断的进行,AOF 的文件会越来越大,文件越大,占用服务器内存和磁盘越大以及 AOF 恢复要求时间越长。

AOF文件中可能会有一些无意义的命令,比如对同一个key进行值的设置 set key1 555 设置了1000次,但是其实只要执行最后一次的命令就能达到结果,或者前面又100万条修改key的数据,但是最后一条是flushall命令,恢复数据时前面命令的执行那将没有意义。此时,通过AOF重写可以避免此种无意义的命令执行。并且减小AOF文件大小。

当 AOF 文件的大小超过所设定的阈值时,Redis 就会启动 AOF 文件的内容压缩,只保留可以恢复数据的最小指令集。

AOF 文件重写并不是对原文件进行重新整理,而是直接读取服务器现有的键值对,然后用一条命令去代替之前记录这个键值对的多条命令,生成一个新的文件后去替换原来的 AOF 文件。

配置项解释auto-aof-rewrite-percentage 100默认值为 100。 aof 自动重写配置, 当目前 aof 文件大小超过上一次重写的 aof 文件大小的百分之多少进行重写, 即当 aof 文件增长到一定大小的时候, Redis 能够调用 bgrewriteaof对日志文件进行重写。 当前 AOF 文件大小是上次日志重写得到 AOF 文件大小的二倍(设置为 100) 时, 自动启动新的日志重写过程auto-aof-rewrite-min-size 64mbaof自动重写的最小文件大小,默认64mb,避免了达到约定百分比但尺寸仍然很小的情况还要重写,要两个条件都符合才进行重写,默认也就是目前aof文件达到了上一次重写的aof文件大小的2倍,并且目前文件大小要大于等于64mb才进行重写no-appendfsync-on-rewrite在进行RDB持久化或者AOF重写的时候,是否执行fsync操作,如果设置为yes,那么在进行RDB持久化或者AOF重写的时候,就不会进行fsync操作,无论你设置的是everysec还是always都不会进行fsync操作,如果对应用的延时要求有很高要求的话,可以设置为yes,因为fsync会阻塞redis的主进程,如果在进行RDB持久化或者AOF重写时,会消耗大量的IO资源,从而导致fsync的效率会变低,从而导致redis的正常对外服务性能降低。如果设置为no,那么在RDB持久化和AOF重写期间任然会进行fsync,对数据的安全性更友好,Linux 的默认 fsync策略是 30 秒。 如果设置为yes可能丢失 30 秒数据aof-load-truncatedaof 文件可能在尾部是不完整的, 当 redis 启动的时候, aof 文件的数据被载入内存。 重启可能发生在 redis 所在的主机操作系统宕机后,尤其在 ext4 文件系统没有加上 data=ordered选项, 出现这种现象。 redis 宕机或者异常终止不会造成尾部不完整现象, 可以选择让 redis退出, 或者导入尽可能多的数据。 如果选择的是 yes, 当截断的 aof 文件被导入的时候,会自动发布一个 log 给客户端然后 load尽可能多的数据。如果是 no,用户必须手动 redis-check-aof 修复 AOF文件才可以,否则会报错。 默认值为 yes。

优势:

AOF 持久化的方法提供了多种的同步频率,即使使用默认的同步频率每秒同步一次,Redis 最多也就丢失 1 秒的数据而已。

劣势: 1、对于具有相同数据的的 Redis,AOF 文件通常会比 RDF 文件体积更大(RDB 存的是数据快照) 2、虽然 AOF 提供了多种同步的频率,默认情况下,每秒同步一次的频率也具有较高的性能。在高并发的情况下,RDB 比 AOF 具好更好的性能保证。

持久化策略的取舍

如果可以忍受一小段时间内数据的丢失,毫无疑问使用 RDB 是最好的,定时生成RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。

否则就使用 AOF 重写。但是一般情况下建议不要单独使用某一种持久化机制,而是应该两种一起用,在这种情况下,当 redis 重启的时候会优先载入 AOF 文件来恢复原始的数据,因为在通常情况下 AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整。

最新回复(0)