持久化-数据过期-内存淘汰
约 3377 字大约 11 分钟
2025-01-15
持久化
两种持久化
Redis 提供两种持久化方式,RDB(Redis DataBase 缩写快照)和 AOF(Append Only File);
第一种是 RDB 快照,第二种是 AOF 日志
RDB:
- RDB 快照是一次全量备份
- 快照是内存数据的二进制序列化形式,在存储上非常紧凑
AOF:
- AOF 是连续的增量备份
- AOF 日志记录的是内存数据修改的指令记录文本
RDB 机制
定义
- RDB 快照就是把数据以快照的形式保存在磁盘上,是某个时间点的一次全量数据备份,以二进制序列化形式的文件存储,并且在存储上非常紧密。
- RDB 持久化是指在指定的时间间隔内将内存中的数据集以快照的方式写入磁盘,并保存到一个名为 dump.rdb 的二进制文件中
- RDB 是默认的持久化方式,它恢复时是将快照文件从磁盘直接读到内存里
RDB 触发机制
RDB 来说持久化触发机制有三种:save、bgsave、自动化触发
1、save 命令触发
- 该命令会阻塞当前 Redis 服务器,执行 save 命令期间,Redis 不能处理其他命令,直到 RDB 完成为止
2、bgsave 命令触发
- 执行 bgsave 命令时,Redis 主进程会 fork 一个子进程来完成 RDB 的过程,会先将数据写入到一个临时二进制文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件(可以理解为 Copy On Write 机制)。
- Redis 主进程阻塞时间只有 fork 阶段的那一下。相对于 save,阻塞时间很短。基本上 Redis 内部所有的 RDB 操作都是采用 bgsave 命令。
- fork 的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。
3、自动触发
自动触发是可以在 redis.conf 配置文件中修改,默认达到 以下三种条件,就会自动触发持久化,触发后,底层调用的其实还有 bgsave 命令:
举例:1 分钟内改了 1 万次,5 分钟内改了 10 次,或 15 分钟内改了 1 次。
- save 900 1 #在 900 秒(15 分钟)之后,如果至少有 1 个 key 发生变化,则 dump 内存快照。
- save 300 10 #在 300 秒(5 分钟)之后,如果至少有 10 个 key 发生变化,则 dump 内存快照。
- save 60 10000 #在 60 秒(1 分钟)之后,如果至少有 10000 个 key 发生变化,则 dump 内存快照。
如果不需要持久化机制,则可以注释掉所有的 save 命令
bgsave 执行流程
- 执行 bgsave 命令的时候,Redis 主进程会检查是否有子进程在执行 RDB/AOF 持久化任务,如果有的话,直接返回,防止两个进程同时对磁盘进行写入操作
- Redis 主进程fork 一个子进程来执行执行 RDB 操作,fork 操作会对主进程造成阻塞(影响 Redis 的读写),fork 操作完成后会发消息给主进程,从而不再阻塞主进程
- RDB 子进程把 Redis 主进程的内存数据,写入到一个临时的快照文件,持久化完成后,再使用临时快照文件替换掉原来的 RDB 文件。(该过程中主进程的读写不受影响,但Redis 的写操作不会同步到主进程的主内存中,而是会写到一个临时的内存区域作为一个副本)
- 子进程完成 RDB 持久化后会发消息给主进程,通知 RDB 持久化完成,并将步骤 3 中的内存副本中的增量写数据同步到主内存
优缺点
优点:
- RDB 文件紧凑,全量备份,非常 适合用于进行备份和灾难恢复。
- 对于大规模数据的恢复,且对于数据恢复的完整性不是非常敏感的场景,RDB 的恢复速度要比 AOF 方式更加的高效。
- 生成 RDB 文件的时候,redis 主进程会 fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘 IO 操作。
缺点:
- 占用空间大:fork 的时候,内存中的数据被克隆了一份,大致 2 倍的膨胀性需要考虑。
- 当进行快照持久化时,会开启一个子进程专门负责快照持久化,子进程会拥有父进程的内存数据,父进程修改内存子进程不会反应出来,所以在快照持久化期间修改的数据不会被保存,可能丢失数据。
- 在一定间隔时间做一次备份,所以如果 redis 意外 down 掉的话,就会丢失最后一次快照后的所有修改。
AOF 机制
定义
- 每次都使用 RDB 机制全量备份的方式是非常耗时间的,因此 Redis 还提供了另一种持久化机制 AOF(append only file)。
- AOF 日志是持续增量的备份,将 Redis 执行过的每个 写操作以日志的形式记录下来 (读操作不记录),只许追加文件但不可以改写文件(appendonly.aof 文件)。
- redis 启动的时候会读取该文件进行数据恢复,根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
AOF 触发机制
- 每修改同步:appendfsync always 同步持久化,每次发生数据变更会被立即记录到磁盘,性能较差但数据完整性比较好。
- 每秒同步:appendfsync everysec 异步操作,每秒记录,如果一秒内宕机,有数据丢失。
- 不同步:appendfsync no 从不同步
优点
优点:
- 数据安全,AOF 持久化可以配置 appendfsync 属性,有 always 属性,每进行一次命令操作就记录到 AOF 文件中一次;
- 通过 append 模式写文件,即使中途服务器宕机 ,可以通过 redis-check-aof 工具解决数据一致性问题;
- AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令 进行合并重写),可以删除其中的某些命令(比如误操作的 flushall);
缺点:
- AOF 文件比 RDB 文件大,且恢复速度慢;
- 数据集大的时候,比 RDB 启动效率低;
注:如果同时开启两种持久化方式,在这种情况下,当 redis 重启的时候会优先载入 AOF 文件来恢复原始的数据,因为在通常情况下 AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整。
Redis4.0 的混合持久化(待理解)
- 仅使用 RDB 快照方式恢复数据,由于快照时间粒度较大时,会丢失大量数据。
- 仅使用 AOF 重放方式 恢复数据,日志性能相对 rdb 来说要慢。在 Redis 实例很大的情况下,启动需要花费很长的时间。
为了解决这个问题,Redis4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。RDB 文件的内容和增量的 AOF 日志文件存在一起,这里的 AOF 日志不再是全量的日志,而是自持久化开始到持久化结束的这段时间发生的增量 AOF 日志,通常这部分 AOF 日志很小
- 大量数据使用粗粒度(时间上)的 rdb 快照方式,性能高,恢复时间快。
- 增量数据使用细粒度(时间上)的 AOF 日志方式,尽量保证数据的不丢失。
在 Redis 重启时,进行 AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点,快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。
另外,可以使用下面这种方式:Master 使用 AOF,Slave 使用 RDB 快照,master 需要首先确保数据完整性,它作为数据备份的第一选择;slave 提供只读服务或仅作为备机,它的主要目的就是快速响应客户端 read 请求或灾切换。
RDB 与 AOF 对比
| RDB | AOF | |
|---|---|---|
| 定义 | RDB 持久化,是将内存中的数据以快照的形式保存在磁盘上,是某个时间点的一次全量数据备份 | AOF 日志是持续增量的备份,将 Redis 执行过的每个 写操作,记录到 appendonly.aof 日志 |
| 触发机制 | save、bgsave、自动触发(redis.conf 设置) | appendfsync always、appendfsync everysec、appendfsync no |
| 优点 | 性能高(恢复时间快)、文件占磁盘小(每次覆盖) | 数据安全 |
| 缺点 | 数据不安全、占用内存 | 文件占磁盘大(追加),恢复时间慢 |
| 适用场景 | 适用全量数据、大规模数据备份恢复 | 适用敏感数据 |
- AOF 文件比 RDB 更新频率高,优先使用 AOF 还原数据;
- AOF 比 RDB 更安全也更大;
- RDB 性能比 AOF 好;
- 如果两个都配了优先加载 AOF;
我们在 set key 的时候,可以给它设置一个过期时间,比如 expire key 60。指定这 key60s 后过期。
60s 后,redis 是如何处理的嘛?我们先来介绍几种过期策略:
立即过期:
- 每个设置过期时间的 key 都需要创建一个定时器,到过期时间就会立即对 key 进行清除。该策略可以立即清除过期的数据,对内存很友好;
- 但是会占用大量的 CPU 资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
惰性过期
- 只有当访问一个 key 时,才会判断该 key 是否已过期,过期则清除。该策略可以最大化地节省 CPU 资源,却对内存非常不友好。
- 极端情况可能出现大量的过期 key 没有再次被访问,从而不会被清除,占用大量内存。
定期过期
- 每隔一定的时间,会扫描一定数量的数据库的 expires 字典中一定数量的 key,并清除其中已过期的 key。
- 该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得 CPU 和内存资源达到最优的平衡效果。
Redis 的数据过期采取策略:同时使用了惰性过期和定期过期
**问题场景:**假设 Redis 当前存放 30 万个 key,并且都设置了过期时间,如果你每隔 100ms 就去检查这全部的 key,CPU 负载会特别高,最后可能会挂掉。
- 因此,redis 第一采取定期过期策略,每隔 100ms 就随机抽取一定数量的 key 来检查和删除的。
- 但是呢,最后可能会有很多已经过期的 key 没被删除。这时候,redis 采用惰性删除。在你获取某个 key 的时候,redis 会检查一下,这个 key 如果设置了过期时间并且已经过期了,此时就会删除
但是呀,如果定期删除漏掉了很多过期的 key,然后也没走惰性删除。就会有很多过期 key 积在内存内存,直接会导致内存爆的。或者有些时候,业务量大起来了,redis 的 key 被大量使用,内存直接不够了,运维小哥哥也忘记加大内存了。
难道 redis 直接这样挂掉?不会的!Redis 用8 种内存淘汰策略保护自己~
内存淘汰机制
8 种:lru 两个、lfu 两个、随机两个、根据过期时间淘汰、报错
- volatile-lru:当内存不足以容纳新写入数据时,从设置了过期时间的 key中使用 LRU(最近最少使用)算法进行淘汰;
- allkeys-lru:当内存不足以容纳新写入数据时,从所有 key中使用 LRU(最近最少使用)算法进行淘汰。
- volatile-lfu:4.0 版本新增,当内存不足以容纳新写入数据时,在过期的 key 中,使用 LFU(最少频率)算法进行删除 key。
- allkeys-lfu:4.0 版本新增,当内存不足以容纳新写入数据时,从所有 key 中使用 LFU 算法进行淘汰;
- volatile-random:当内存不足以容纳新写入数据时,从设置了过期时间的 key 中,随机淘汰数据;。
- allkeys-random:当内存不足以容纳新写入数据时,从所有 key 中随机淘汰数据。
- volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的 key 中,根据过期时间进行淘汰,越早过期的优先被淘汰;
- noeviction:默认策略,当内存不足以容纳新写入数据时,新写入操作会报错。
版权所有
版权归属:haipeng-lin