前言
Redis 是一个内存数据库,把数据存储在内存中,而内存中的数据是不持久的,要想数据持久就得将数据存储到硬盘中,而 Redis 相比于 Mysql 这样的关系型数据库最大的优势就在于将数据存储在内存中从而效率更高,速度更快
所以小孩子才做选择,我全都要。Redis 为了保证效率和持久性,于是将一份数据同时存储到内存和硬盘中,有同学可能会疑惑,将数据同时存储到内存和硬盘效率不就低了吗?实际上将数据存储到硬盘有不同的策略,能够保证效率依然很高。
当要查询某个数据时,就从内存中读取。而硬盘中的数据相当于是一个备份,当内存中的数据因为某些原因丢失后就可以通过硬盘恢复。
实现持久化的策略
Redis ⽀持 RDB 和 AOF 两种持久化机制。持久化功能有效地避免因进程退出造成数据丢失问题, 当下次重启时利⽤之前持久化的⽂件即可实现数据恢复。
RDB:相当于定期备份,每隔一段规定时间,便对数据进行一次整体的备份
AOF:相当于实时备份,每当 Redis 中更新了数据,便将数据进行备份
RDB
.rdb 文件所在的路径
通过 Redis 的配置文件 redis.conf 可以查看 .rdb 文件所在的路径
可以看到 .rdb 文件默认存储在路径:
cd /var/lib/redis
.rdb 文件的名称
通过 Redis 的配置文件 redis.conf 可以查看 .rdb 文件的名称
在上述的路径中我们就可以找到 dump.rdb 文件
RDB 持久化是把当前进程数据生成快照保存到硬盘的过程。
RDB 会定期的将内存中的数据生成快照保存到硬盘中,后续 Redis 一旦重启了(数据丢失了)就会根据硬盘中保存的快照来恢复数据
在 RDB 机制中,备份数据的时机分为“手动触发” 和 “自动触发”
手动触发
程序员通过 Redis 客户端,执行特定的命令,来触发快照的生成,进行数据备份
sava 命令
执行 sava 命令,Redis 会全力以赴的进行“快照生成”的操作,由于 Redis 是单线程,所以就会导致其他客户端的命令无法执行,造成阻塞。可能会导致严重的后果,所以一般不使用该命令
bgsave 命令
Redis 进程执⾏ fork 操作创建⼦进程,RDB 持久化过程由⼦进程负责,完成后⾃动结束。阻塞只发⽣在 fork 阶段,⼀般时间很短。
Redis 内部的所有涉及 RDB 的操作都采⽤类似 bgsave 的⽅式。
bgsave 命令流程说明
1.执⾏ bgsave 命令,Redis ⽗进程判断当前是否存在其他正在执⾏的⼦进程,如 RDB/AOF ⼦进程,如果存在(说明其他 Redis 客户端已经发起了备份数据的请求) bgsave 命令直接返回。
2.⽗进程执⾏ fork 创建⼦进程,fork 过程中⽗进程会阻塞(但时间很短),fork 创建子进程的过程简单粗暴,直接把当前的进程复制一份作为子进程,子进程会复制父进程的 PCB,内存中的数据,文件描述符表等... ,所以子进程的内存中拥有和父进程一样的数据,所以安排子进程备份内存中的数据和备份父进程中的数据一样。
有同学可能会想,子进程复制了父进程中的数据,如果父进程中的数据很多,那么复制出来的子进程也会占用内存中的大量资源。但其实不会!
对于子进程和父进程中相同的数据,不会为子进程重新拷贝一份,而是让两个进程共同使用一份内存数据,只有子进程和父进程中的数据不同时,才会进行真正的数据拷贝。
3.⽗进程 fork 完成后,bgsave 命令返回 "Background saving started" 信息并不再阻塞⽗进程,可以继续响应其他命令。
4.⼦进程创建 RDB ⽂件,根据内存中的数据⽣成临时快照⽂件,完成后对原有⽂件进⾏替换。
5.进程发送信号给⽗进程表示完成,⽗进程更新统计信息。
自动触发
注意:RDB 文件在备份时会创建一个新的 RDB 文件来替换原来的文件
通过 Redis 的配置文件 redis.conf 可以查看在达到何种条件时会自动触发 RDB 文件的备份。
save 900 1 代表在 900s 内进行了一次修改就进行备份,同理 save 300 10 代表在 300s 内进行了 10 次修改就进行备份
我们可以修改上面的配置来控制自动备份的时机,但要注意一个原则:不能频繁的进行 RDB 文件的备份,因为备份一次 RDB 文件会造成较大的开销。
由于备份 RDB 文件不能太频繁,所以也导致 RDB 快照中备份的数据和实际 Redis 中的数据存在一定的偏差,如果在两次备份间 Redis 出现了问题导致数据丢失,就无法进行恢复。而 AOF 就是解决这个问题的方案
正常退出触发
除了在达到时间和修改次数要求后进行自动备份,在 Redis 服务器正常关闭时也会触发 RDB 文件的自动备份。
但如果 Redis 服务器异常关闭,如 kill -9 杀死 Redis 服务器进程,或者服务器掉电,此时 Redis 服务器就来不及进行 RDB 文件的自动备份,在上次备份后所进行的操作就会丢失。
主从复制触发
Redis 进行主从复制时,主节点会自动的生成 RDB 快照,然后把 RDB 快照文件传给子节点,子节点就能根据 RDB 快照文件获取父节点中的数据
执行 flushall 命令
执行 flushall 命令也会同步将 RDB 文件清空
RDB 文件损坏会怎样?
这个问题首先要看 RDB 文件损坏在哪里,如果是将 RDB 文件的末尾改坏了(比如在文件末尾添加了一些数据)那么前面的内容不会受到影响,此时可以正常的启动 Rdis 服务器,也可以读出 RDB 文件中备份的正确数据。
但要是中间位置的数据被破坏,那么大概率 Redis 服务器无法正常启动(因为 Redis 服务器启动时会读取 RDB 文件中的内容),即使 Redis 服务器正常启动了,数据也大概率会有错误,此时数据是不可靠的。
检查 RDB 文件是否损坏
那么在得到一个 RDB 文件时,我们就要考虑这个 RDB 文件是否有损坏,如果有损坏就不能使用,避免对 Redis 中原来的数据造成影响,我们该如何检查 RDB 文件是否损坏呢?
通过下述命令检查
redis-check-rdb /var/lib/redis/dump.rdb
如图,当 RDB 文件有损坏时就会报错。
RDB 的优缺点
• RDB 是⼀个紧凑压缩的⼆进制⽂件,代表 Redis 在某个时间点上的数据快照。⾮常适⽤于备份,全量复制等场景。⽐如每 6 ⼩时执⾏ bgsave 备份,并把 RDB ⽂件复制到远程机器或者⽂件系统中 (如 hdfs )⽤于灾备。
• Redis 加载 RDB 恢复数据远远快于 AOF 的⽅式。
• RDB ⽅式数据没办法做到实时持久化 / 秒级持久化。因为 bgsave 每次运⾏都要执⾏ fork 创建⼦进 程,属于重量级操作,频繁执⾏成本过⾼。
• RDB ⽂件使⽤特定⼆进制格式保存,Redis 版本演进过程中有多个 RDB 版本,兼容性可能有⻛ 险。
AOF
AOF 会将用户的每一步操作都保存到文件中,当 Redis 服务器重新启动的时候就会读取 AOF 文件中的内容来还原数据。
AOF 相关配置
AOF 默认是关闭状态,可以通过修改 Redis 的配置文件来进行修改:
如上图,appendonly 配置项决定了 AOF 是否开启,注意:当 AOF 开启后 RDB 便失效了,Redis 启动时只会读取 .aof 文件中的内容进行数据恢复。
appendfilename 配置项决定了 .aof 文件的文件名,而 appendonly.aof 文件所在的位置和 .rdb 文件相同,在 /var/lib/redis。
AOF 工作流程
1. 所有的写⼊命令会追加到 aof_buf(缓冲区)中。
2. AOF 缓冲区根据对应的策略向硬盘做同步操作。
3. 随着 AOF ⽂件越来越⼤,需要定期对 AOF ⽂件进⾏重写,达到压缩的⽬的。
4. 当 Redis 服务器启动时,可以加载 AOF ⽂件进⾏数据恢复。
为什么需要 aof_buf(缓冲区)
如果不使用缓冲区,每当用户传入一个命令就需要改变内存中数据的同时将命令写入到硬盘上的 .aof 文件中,这就说明,当 Redis 收到一百条命令就需要读写硬盘 100 次,频繁读取硬盘会造成严重的资源浪费。
所以可以先将命令保存到缓冲区中,保存一批命令后再全部将其保存到硬盘中,这大大减少了读写硬盘的次数,提高了性能。
问题:可能有同学会疑惑,如果缓冲区中的内容还没来得及保存到硬盘中,Redis 服务器就崩了,那缓冲区中的数据不就丢失了吗?
确实是这样,缓冲区中没来得及保存到硬盘的数据确实会丢失。但这也没办法,我们可以设置在缓冲区中一收到数据就同步给硬盘,但这很显然会导致读写硬盘次数增加,性能降低。但鱼和熊掌不可兼得。得到性能就会丢失数据可靠性,反之亦然。
AOF 缓冲区同步⽂件策略
always 表示命令一进入缓冲区就同步到硬盘中,这是频率最高,数据可靠性最高,性能最低的方法。
everysec 是默认配置,表示每秒同步一次缓冲区中的数据到硬盘中,这个策略兼顾了可靠性和安全性,理论上最多丢失 1 秒的数据。
no 表示由操作系统来控制同步频率,但操作系统同步策略不可控,所以数据可靠性最低,一般不建议使用。
设置 AOF 缓冲区同步⽂件策略
在 redis.conf 配置文件(/etc)中可以设置 AOF 缓冲区同步⽂件策略:
重写机制
随着命令不断写⼊ AOF,⽂件会越来越⼤,为了解决这个问题,Redis 引⼊ AOF 重写机制压缩⽂件体积。AOF ⽂件重写是把 Redis 进程内的数据转化为写命令同步到新的 AOF ⽂件。
重写后的 AOF 为什么可以变⼩?
有如下原因:
• 进程内已超时的数据不再写入⽂件。
• 旧的 AOF 中的⽆效命令,例如 del、hdel、srem 等重写后将会删除,只需要保留数据的最终版 本。
• 多条写操作合并为⼀条,例如 lpush list a、lpush list b、lpush list c从可以合并为 lpush list a b c。较⼩的 AOF ⽂件⼀⽅⾯降低了硬盘空间占⽤,⼀⽅⾯可以提升启动 Redis 时数据恢复的速度。
AOF 重写过程可以⼿动触发和⾃动触发
• ⼿动触发:调⽤ bgrewriteaof 命令。
• ⾃动触发:根据 auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 参数确定⾃动触发时机。
◦ auto-aof-rewrite-min-size:表⽰触发重写时 AOF 的最⼩⽂件⼤⼩,默认为 64MB。
◦ auto-aof-rewrite-percentage:代表当前 AOF 占⽤⼤⼩相⽐较上次重写时增加的⽐例。
AOF 重写的流程
1. 执⾏ AOF 重写请求。
如果当前进程正在执⾏ AOF 重写,请求不执⾏。如果当前进程正在执⾏ bgsave(RDB 定期备份) 操作,重写命令延迟到 bgsave 完成之后再执⾏。
2. ⽗进程执⾏ fork 创建⼦进程。由子进程来完成重写操作,父进程继续对客户端提供服务
3. 重写
a. 主进程 fork 之后(复制出子进程后),继续响应其他命令。所有修改操作写⼊ AOF 缓冲区并根据 appendfsync 策略同步到硬盘,保证旧 AOF ⽂件机制正确。
b. ⼦进程只有 fork 之前的所有内存信息,⽗进程中需要将 fork 之后这段时间的修改操作写⼊AOF 重写缓冲区中。
4. ⼦进程根据内存快照,将命令合并到新的 AOF ⽂件中。
5. ⼦进程完成重写
a. 新⽂件写⼊后,⼦进程发送信号给⽗进程。
b. ⽗进程把 AOF 重写缓冲区内临时保存的命令追加到新 AOF ⽂件中。
c. ⽤新 AOF ⽂件替换⽼ AOF ⽂件。
为什么子进程在重写时,还要把数据保存到旧 AOF 文件
当子进程在根据内存中的数据重写 AOF 文件时,父进程响应的命令是子进程没有的,所以将这部分命令保存到缓冲区中,后续再将其保存到新 AOF 文件中,新 AOF 文件会替代旧 AOF 文件
既然旧 AOF 文件会被替换,那为什么还要将保存到缓冲区的数据保存到旧 AOF 文件呢?为了避免极端情况,若子进程在重写的时候,Redis 服务器突然崩掉就会导致保存到缓冲区中的数据丢失,如果数据也保存到了旧 AOF 文件中,就没有什么大碍了。
AOF 收集了子进程重写期间主进程处理的命令,但 RDB 没有管
相信有细心的小伙伴发现了,重写 AOF 文件时,专门用了一个缓冲区来保存子进程重写期间主进程所处理的命令,来保证所有的命令都被保存到 AOF 文件中,但 RDB 并没有理会子进程重写期间主进程处理的命令,它将这段时间的改变全部抛弃了。
这是为什么?因为 RDB 是定期备份,这就代表它无法与最新的数据保持同步,而 AOF 是实时备份,它需要保证 AOF 文件中的内容与最新的数据保持同步。没有好坏之分,还是得看我们的业务需求
混合持久化
AOF 文件中是字符数据,而 RDB 文件中是二进制数据,这就导致 Redis 读取 RDB 文件恢复数据的速度较快,那么我们不如让 AOF 重写的时候按照二进制来组织数据,因为重写也是按照 Redis 内存中的数据重写的,并不在乎过程。
事实上 Redis 的开发人员们也确实这样做了,Reids 采用了混合持久化的方式,结合了 AOF 和 RDB 的优点。
按照 AOF 的方式,将每一个操作都通过文本的方式记录到文件,在 AOF 文件重写时,就按照 RDB 的二进程方式将内存中的数据保存到新的 AOF 文件中,后续再进行的操作,依然是将命令通过文本形式追加在文件后面。
当 AOF 和 RDB 文件同时存在,用谁还原数据
肯定是用 AOF ,因为 AOF 是实时备份,数据比 RDB 文件的定期备份更全。