redis持久化
为了防止数据丢失,redis需要将数据从内存dump到磁盘,也就是redis持久化,redis持久化有两种方式RDB和AOF
注意:我使用的版本是6.0.10,不同版本可能略有差别
RDB
RDB是指Redis DataBase,在指定的时间间隔内,将内存中数据的快照写入到磁盘dump.rdb的文件中,恢复时通过载入rdb文件来还原数据库状态。
rdb文件是一个经过压缩的二进制文件
redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到临时文件,持久化结束之后,将临时文件替换上次持久化好的文件(保存在dump.rdb文件中),RDB方式比AOF更加高效,但是可能会丢失最后一次持久化的数据
在这里fork的进程是根据当前进程fork的,包含了当前进程的所有数据、变量等
Redis启动后会读取RDB快照文件,将数据从硬盘载入到内存。
RDB配置
关于RDB的配置是在redis.conf中的快照(SNAPSHOTTING)模块中
1 | # |
rdb文件会存储在执行redis-cli的当前目录下的dump.rdb文件中,默认触发条件是
save 900 1 #15分钟内修改了1次
save 300 10 #5分钟内修改了10次
save 60 10000 #1分钟内修改了10000次当达到条件时会触发bgsave命令
这个我测试了一下 save 300 3表示的是自上次生成rdb快照后,300s内如果有3次更改,在300s的时间节点时会触发一次bgsave命令,而不是写入3次后就立马触发
也可以使用save或者bgsave命令来直接立即备份。
save命令是由主线程来做的,其他操作都会阻塞,直到rdb文件创建完毕,但是不会消耗额外的内存,如果数据比较多的话,会导致redis较长时间不响应,所以要尽量避免在生产环境中使用
bgsave是后台线程做的,其他操作可以正常执行,不会阻塞客户端命令,如果想要知道快照是否完成,可以通过lastsave命令获取最近一次成功执行快照的时间,返回结果是一个时间戳
可以使用命令来检测rdb文件是否存在问题
1 | redis-check-rdb |
重启之后会直接加载本目录下的dump.rdb文件
bgsave命令
使用fork创建子进程;父进程继续接收并处理客户端发来的命令,子进程将内存数据写入临时文件;子进程写入所有数据后会用临时文件替换旧RDB文件
在进行快照过程中不会修改RDB文件,只有快照结束后才会将旧的文件替换掉
bgsave命令在执行时,服务器处理save、bgsave、bgrewriteaof这三个命令会与平时不同
- save命令不可执行,避免父子进程同时执行rdbsave,防止产生竞争条件
- bgsave命令不可执行,防止两个bgsave产生竞争条件
- 如果此时bgsave正在执行,bgrewriteaof会被延迟到bgsave执行完之后执行;如果bgrewriteaof正在执行,则bgsave命令会被拒绝, 为了防止两个子进程同时执行造成大量的磁盘写入操作
原理
使用fork()+copyonwrite
fork()
fork()是linux和unix的底层api,会fork出来一个子进程,共享那一刻的内存数据,且修改互相不可见
copyonwrite
写时复制策略,主进程fork子进程之后,父子进程共享内存中的代码段和数据段,此时内存几乎没有增长。子进程在做数据持久化的过程中,只会进行遍历读取,但是父进程必须服务客户端请求,可能会对数据进行修改,此时使用COW(copy on write)机制,当父进程写数据时,将该内存页复制一份父进程来操作修改,其余的还是在共享内存内。随着父进程持续的修改数据,越来越多的共享页面被分离出来,内存会持续增长,但是最多也不会超过原有数据内存的2倍
每一页是4K
优缺点
优点
- 数据紧凑,比较小,可以很方便的传送到另一个数据中心
- 适合大规模的数据恢复
- 对数据完整性和一致性要求不高
缺点
- 由于是每隔一段时间去备份的,如果redis意外挂掉的话,可能会丢失最后一次快照的所有修改
- 耗时耗性能,每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步脏数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘io操作,可能会严重影响性能
动态停止RDB持久化的方式
1 | redis-cli config set save "" |
在进行rdb持久化是将内存数据完整的写入磁盘,并不是使用增量的方式,如果数据量大的话可能会有大量的磁盘IO操作
查看rdb是否打开
1 | redis-cli config get save |
如果结果返回是空的话,则为关闭
AOF
AOF是指Append Only File,以日志的形式记录写操作,只许追加文件内容,不许修改,redis启动时会读取该文件来根据日志文件内容重新将写指令执行一次以完成数据恢复(日志文件名称为appendonly.aof)
默认该方式没有开启
AOF配置
关于AOF的配置是在redis.conf中的APPEND ONLY MODE模块中
1 | ############################## APPEND ONLY MODE ############################### |
如果aof文件中存在一些错误的指令(由于网络原因等问题导致命令没有写完),可以使用命令进行修复aof文件
1 redis-check-aof --fix
aof比rdb具有更好的持久化性,在使用aof持久化方式时,redis会将每一个收到的写命令通过write函数追加到文件中,在redis重启时会将aof文件中的命令重新执行一遍
AOF实现
AOF持久化方式打开之后,服务器执行写命令会写在缓冲区的末尾(server.c中的redisServer中有一个aof_buf字段来作为缓冲区),serverCron函数会定时将aof_buf缓冲区中的数据写入到文件
重写机制
由于AOF采用的是日志追加,可能会导致日志文件越来越大,为了避免此情况,增加了重写机制,当AOF文件超过所设定的阈值之后,redis会启动AOF文件的压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof来执行
重写之后减少了磁盘占有量,而且可以加速数据恢复
redis在进行重写时会占用大量的cpu和内存资源
no-appendfsync-on-rewrite no 这个配置是指是否在每次fsync的时候都进行重写,但是这样会造成大量的磁盘IO,还有可能会造成对写操作的阻塞,所以一般该值不推荐修改为yes
重写步骤:
- redis调用fork,创建一个重写子进程,子进程将整个内存中的数据库内容用命令的方式重写了一个新的aof文件写入到一个临时文件中
- 父进程在处理client的时候,除了将写命令写入原来的aof文件中,还需要同时将写命令缓存起来(aof_rewrite_buf_blocks),写入原来的aof文件中是为了保证如果子进程重写失败不会出问题
- 当子进程把快照内容写完之后通知父进程,父进程将缓存的写命令追加写入临时文件
- 追加结束后父进程将临时文件替换老的aof文件,之后收到的命令也将写入新的aof文件中
AOF文件修复
服务器可能在程序正在对AOF文件进行写入时停机,造成AOF文件出错,可以使用redis-check-aof --fix readonlu.aof
来进行修复
优缺点
优点
- 写入日志的策略分为always、everysec、no,使用默认everysec,最多损失1秒的数据
- AOF文件有序的保存了对redis执行的所有操作,如果误操作了flushall命令,只要aof文件还没有被重写,那么只要停止服务器,删掉AOF文件末尾的flushall命令,重启redis,就可以将数据恢复到flushall之前的状态
缺点
aof文件远大于rdb文件,恢复速度慢
由于aof方式比rdb方式的更新频率高,所以如果开启了aof,那么在启动时会优先使用aof文件来还原数据库状态
动态关闭AOF持久化的方式
1 | redis-cli config set appendonly no |
混合模式
在redis4.0之后出现了rdb和aof混合模式,而且默认开模式是开启的
1 | aof-use-rdb-preamble yes |
这个是基于aof开启的时候才会生效
开启后,AOF在重写时会直接读取RDB中的内容
运行过程:
通过bgrwriteaof完成,不同的是当开启混合持久化后,子进程会把内存中的数据以RDB的方式写入aof中,把重写缓冲区中的增量命令以AOF方式写入到文件,将含有RDB格式和AOF格式的AOF数据覆盖旧的AOF文件
新的AOF文件中,一部分数据来自RDB文件,一部分来自Redis运行过程时的增量数据
一般redis的主节点不进行持久化操作,而是将持久化放在从节点进行。从节点是备份节点,没有来自客户端请求的压力,它的操作系统资源往往比较充沛
定时任务
在server.c中的serverCron函数是redis的一个定时任务,该函数处理了很多定时处理的命令。可以在配置文件中配置hz的值来配置每秒执行多少次,默认是10,即1秒执行10次,表示100ms一次
1 | /* This is our timer interrupt, called server.hz times per second. |