前文
本文是本人的第一篇博客,也是借鉴了很多技术大佬的博客和官网文档,博客的排版用的也不是很熟练,还望多多谅解。
写博客的目的很简单,也是为了督促自己更有效的学习,总结所学的知识点。如有不足,还请各位大佬多指教,希望借此得到进步,感谢!
目录
一、持久化目的
Redis是内存数据库,所有数据都是存储在内存中的,如果不定期将Redis中的数据库状态保存到磁盘,那么一旦服务器进程退出,就会导致数据的永久丢失。当下次Redis重启时,利用持久化文件实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置。
二、持久化分类
Redis的持久化分为两种:
- RDB持久化(快照)
- AOF持久化(写命令)
1.RDB持久化
1.1.概念
又称快照持久化,是将当前进程汇总的数据生成快照保存到硬盘,保存的文件后缀名为rdb。
1.2.触发条件
- 手动触发
- 自动触发
1.2.1.手动触发
手动触发有两个命令save和bgsave
- save命令:会阻塞服务器进程,直到快照文件创建完成为止。阻塞期间服务器不能处理任何命令要求
- bgsave命令:新建子进程,用于生成rdb文件,与此同时,redis主进程继续处理请求,因此不会阻塞服务器进程(可以通过lastsave命令查看是否成功生成rdb文件)
1.2.2.自动触发
通过配置文件,能够设置redis自动触发bgsave;
save m n 当m秒内发生n次变化时,自动触发
save 900 1
save 300 10
save 60 10000
含义:
Redis 服务器在 900 秒之内,对数据库进行了至少一次修改
Redis 服务器在 300 秒之内,对数据库进行了至少 10 次修改
Redis 服务器在 60 秒之内,对数据库进行了至少 10000 次修改
若配置文件中有多条save命令时,只要满足任意一个,bgsave就会被执行
实现原理:
Redis的save m n,是通过serverCron函数、dirty计数器、和lastsave时间戳来实现的。
serverCron是Redis服务器的周期性操作函数,默认每隔100ms执行一次;该函数对服务器的状态进行维护,其中一项工作就是检查 save m n 配置的条件是否满足,如果满足就执行bgsave。
dirty计数器是Redis服务器维持的一个状态,记录了上一次执行bgsave/save命令后,服务器状态进行了多少次修改(包括增删改);而当save/bgsave执行完成后,会将dirty重新置为0。
例如,如果Redis执行了set mykey helloworld,则dirty值会+1;如果执行了sadd myset v1 v2 v3,则dirty值会+3;注意dirty记录的是服务器进行了多少次修改,而不是客户端执行了多少修改数据的命令。
lastsave时间戳也是Redis服务器维持的一个状态,记录的是上一次成功执行save/bgsave的时间。
save m n的原理如下:每隔100ms,执行serverCron函数;在serverCron函数中,遍历save m n配置的保存条件,只要有一个条件满足,就进行bgsave。对于每一个save m n条件,只有下面两条同时满足时才算满足:
(1)当前时间-lastsave > m
(2)dirty >= n
配置文件rdb相关其他参数设置
stop-writes-on-bgsave-error 持久化过程中发生错误时是否继续处理Redis执行命令,默认不处理
rdbcompression 是否压缩rdb文件,需要消耗一些CPU资源,默认不压缩
rdbchecksum 保存rdb文件的时候,进行错误检查校验,在写入文件和读取文件时都起作用;
关闭checksum在写 入文件和启动文件时大约能带来10%的性能提升,但是数据损坏时无法发现
dir ./ rdb文件保存的目录
dbfilename dump.rdb rdb文件名
1.3.执行日志
下图是save m n触发bgsave执行时,服务器打印日志的情况:
1.4.其他触发机制
除了save m n以外,还有一些其他情况会触发bgsave:
-
在主从复制场景下,如果从节点执行全量复制操作,则主节点会执行bgsave命令,并将rdb文件发送给从节点;
-
执行shutdown命令时,自动执行rdb持久化,如下图所示:
1.5.执行流程
图片中的5个步骤所进行的操作如下:
-
Redis父进程首先判断:当前是否在执行save,或bgsave/bgrewriteaof的子进程,如果在执行则bgsave命令直接返回。bgsave/bgrewriteaof 的子进程不能同时执行,主要是基于性能方面的考虑:两个并发的子进程同时执行大量的磁盘写操作,可能引起严重的性能问题
-
父进程执行fork操作创建子进程,这个过程中父进程是阻塞的,Redis不能执行来自客户端的任何命令
-
父进程fork后,bgsave命令返回”Background saving started”信息并不再阻塞父进程,并可以响应其他命令
-
子进程创建RDB文件,根据父进程内存快照生成临时快照文件,完成后对原有文件进行原子替换
-
子进程发送信号给父进程表示完成,父进程更新统计信息
1.6.启动时加载
由于AOF的优先级更高,所以只有关闭AOF,才会在Redis服务器启动时检测RDB文件并自动加载。服务器载入RDB文件期间出于阻塞状态,直到载入完成为止
启动日志输出
Redis载入RDB文件时,会对RDB文件进行校验,如果文件损坏,则日志中会打印错误,Redis启动失败
1.7.优缺点
优点:
- 命令bgsave,fork子线程来创建RDB文件,不影响Redis主线程的日常任务处理
- 生成的RDB文件为压缩的二进制文件,方便传输和保存,同时方便将Redis数据库复原到某一时刻,起到备份作用,提高数据安全性
- 恢复大数据集的速度比AOF快
缺点:
- 无法做到实时持久化,bgsave每次执行都会fork子进程,属于重量级操作,频繁执行成本过高,影响性能
- 无法做到数据一致性,如果没有达到生成RDB文件的条件之前服务器奔溃了,那么这部分的数据会丢失
2.AOF持久化
2.1.概念
将Redis执行的每次写命令记录到单独的日志文件中,当Redis重启时再次执行AOF文件中的命令来恢复数据
2.2.开启
Redis服务器默认开启RDB,关闭AOF;要开启AOF,需要在config配置文件中设置
appendonly yes
2.3.配置文件AOF相关其他参数设置
appendonly 是否打开AOF持久化功能
appendfilename AOF文件名称
dir ./ AOF文件所在目录
no-appendfsync-on-rewrite no AOF重写期间是否禁止fsync;如果开启该选项,可以减轻文件重写时CPU和硬盘的负载(尤其是硬盘),但是可能会丢失AOF重写期间的数据;需要在负载和安全性之间进行平衡
auto-aof-rewrite-percentage 100 文件重写触发条件之一
auto-aof-rewrite-min-size 64mb 文件重写触发提交之一
aof-load-truncated yes 如果AOF文件结尾损坏,Redis启动时是否仍载入AOF文件
appendfsync 同步频率
同步策略有三种方式
- always redis执行每个写命令时,都同步写入硬盘,会严重降低redis性能
- everysec 每秒执行一次,显示的在这一秒内执行的命令写入到硬盘。everysec是always与no策略的折中,是性能和数据安全性的平衡,因此是Redis的默认配置,也是我们推荐的配置
- no 不同步到硬盘,让操作系统来决定何时进行同步,通常同步周期为30秒,由于文件同步时间不可控,并且缓冲区的堆积数据会很多,数据安全性无法保证
2.4.执行流程
AOF的执行流程包括:
-
命令追加(append):将Redis的写命令追加到缓冲区aof_buf
-
文件写入(write)和文件同步(sync):根据不同的同步策略将aof_buf中的内容同步到硬盘
-
文件重写(rewrite):定期重写AOF文件,达到压缩的目的
当Redis启动AOF持久化后,会将所有写命令追加到缓冲区aof_buf中,此时还未写入到AOF文件,默认情况下只有当缓冲区被填满或超过指定时限后,才真正的同步到硬盘里。但这样会带来安全问题,所以会调用fsync函数,根据不同的同步策略强制操作系统立即将缓冲区中的数据写入AOF文件中,实现同步,从而确保数据安全性。
AOF文件默认就是不断追加,文件只会越来越大。过大的AOF文件也会导致数据恢复的时间越来越长。因此就有了文件重写。
文件重写是指定期重写或者说压缩AOF文件,减小文件大小。需要注意的是,AOF重写是把Redis进程内的数据转化为写命令,同步到新的AOF文件,不会对旧的AOF文件进行任何读取、写入操作。
重写方法:
-
过期、无效的额数据或命令不再写入文件,例如重复设值或被删除的数据等
-
合并多条命令。不过为了防止单条命令过大造成客户端缓冲区溢出,对于list、set、hash、zset类型的key,并不一定只使用一条命令;而是以某个常量为界将命令拆分为多条。这个常量在redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD中定义,不可更改,3.0版本中值是64。
2.4.1.文件重写的触发
文件重写的触发分为两种:
- 手动触发
- 自动触发
手动触发
直接调用bgrewriteaof命令,该命令的执行与bgsave有些类似:都是fork子进程进行具体的工作,且都只有在fork时阻塞。
此时服务器执行日志如下:
自动触发
根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数,以及aof_current_size和aof_base_size状态确定触发时机。
-
auto-aof-rewrite-min-size:执行AOF重写时,文件的最小体积,默认值为64MB。
-
auto-aof-rewrite-percentage:执行AOF重写时,当前AOF大小(即aof_current_size)和上一次重写时AOF大小(aof_base_size)的比值。
其中,参数可以通过config get命令查看:
状态可以通过info persistence查看:
只有当auto-aof-rewrite-min-size和auto-aof-rewrite-percentage两个参数同时满足时,才会自动触发AOF重写,即bgrewriteaof操作。
自动触发bgrewriteaof时,可以看到服务器日志如下:
2.4.2.文件重写流程
关于文件重写的流程,有两点需要特别注意:
-
重写由父进程fork子进程进行;
-
重写期间Redis执行的写命令,需要追加到新的AOF文件中,为此Redis引入了aof_rewrite_buf缓存。
对照上图,文件重写的流程如下:
-
Redis父进程首先判断当前是否存在正在执行 bgsave/bgrewriteaof的子进程,如果存在则bgrewriteaof命令直接返回,如果存在bgsave命令则等bgsave执行完成后再执行。前面曾介绍过,这个主要是基于性能方面的考虑。
-
父进程执行fork操作创建子进程,这个过程中父进程是阻塞的。
-
父进程fork后,bgrewriteaof命令返回”Background append only file rewrite started”信息并不再阻塞父进程,并可以响应其他命令。Redis的所有写命令依然写入AOF缓冲区,并根据appendfsync策略同步到硬盘,保证原有AOF机制的正确。
由于fork操作使用写时复制技术,子进程只能共享fork操作时的内存数据。由于父进程依然在响应命令,因此Redis使用AOF重写缓冲区(图中的aof_rewrite_buf)保存这部分数据,防止新AOF文件生成期间丢失这部分数据。也就是说,bgrewriteaof执行期间,Redis的写命令同时追加到aof_buf和aof_rewirte_buf两个缓冲区。
-
子进程根据内存快照,按照命令合并规则写入到新的AOF文件。
-
子进程写完新的AOF文件后,向父进程发信号,父进程更新统计信息,具体可以通过info persistence查看。
父进程把AOF重写缓冲区的数据写入到新的AOF文件,这样就保证了新AOF文件所保存的数据库状态和服务器当前状态一致。
使用新的AOF文件替换老文件,完成AOF重写。
2.5.启动时加载
当AOF开启时,Redis启动时会优先载入AOF文件来恢复数据;只有当AOF关闭时,才会载入RDB文件恢复数据。
当AOF开启,且AOF文件存在时,Redis启动日志:
当AOF开启,但AOF文件不存在时,即使RDB文件存在也不会加载(更早的一些版本可能会加载,但3.0不会),Redis启动日志如下:
文件校验
与载入RDB文件类似,Redis载入AOF文件时,会对AOF文件进行校验,如果文件损坏,则日志中会打印错误,Redis启动失败。但如果是AOF文件结尾不完整(机器突然宕机等容易导致文件尾部不完整),且aof-load-truncated参数开启,则日志中会输出警告,Redis忽略掉AOF文件的尾部,启动成功。aof-load-truncated参数默认是开启的:
2.6.AOF优缺点
优点:
- AOF文件可读性交强,也可手动操作写命令
- 支持秒级持久化、兼容性好
- 若AOF文件报错,比如在写AOF文件时redis崩溃了,redis提供了多种恢复AOF文件的方式,例如使用redis-check-aof工具修正AOF文件(一般都是最后一条写命令有问题,可以手动取出最后一条写命令)
缺点:
- AOF文件较大
- 因为AOF知识追加写命令,所以理论上RDB比AOF方式更加健壮
- 对性能影响大
三、参考资料
Redis高可用详解:持久化技术及方案选择(来源:DBAplus社群)
Redis持久化RDB和AOF (来源:COSCHINA)