文章目录
Redis数据持久化分为RDB和AOF两种方式
1. RDB
通过保存键值对,是一种压缩后保存在硬盘上的二进制文件,在启动时只要存在rdb文件就加载数据。备份有两种执行方式:手动执行,根据配置定时执行
1.1 手动执行
通过命令SAVE
,整个过程阻塞Redis进程
1.2 定期执行
通过命令BGSAVE
,派生一个子进程执行保存工作,伪代码如下:
def SAVE():
#保存rdb文件
rdbSave()
def BGSAVE():
#创建子进程
pid = fock()
if pid == 0:
# 子进程保存rdb文件
rdbSave()
# 完成后向父进程发送信号
signal_parent()
else if pid > 0:
# 父进程继续处理命令请求,并通过轮询等待子进程信号
handle_request_and_wait_signal()
else:
# 处理出错情况
handle_fock_error()
1.3 SAVE、BGSAVE、BGREWRITEAOF
- SAVE:服务器进程处理,主动保存
- BGSAVE:子进程处理
- BGREWRITEAOF:AOF执行命令,子进程进行
SAVE
、BGSAVE
禁止同时进行, 如果BGSAVE
正在执行,BGREWRITEAOF
会延迟到BGSAVE
完毕后执行;如果BGREWRITEAOF
正在执行,BGSAVE
禁止执行,原因是处于性能考虑,只发出一个子进程
1.4 BGSAVE执行原理
首先通过向服务器提供以下配置
# 900秒内至少修改一次
save 900 1
# 300秒内至少修改10次
save 300 10
# 60秒内至少修改10000次
save 60 10000
然后Redis将设置的条件保存到redisServer
结构的saveParams
属性中,该属性是一个数组
struct redisServer{
//...
//记录保存条件的数组
struct saveparam *saveparams;
//...
}
struct saveparam {
//秒数
time_t seconds;
//修改数
int changes;
}
以上配置保存起来就是这样:
然后为了计算修改数量和距离上一次保存的时间间隔,redisServer
又搞了个dirty
计数器和lastsave
属性
struct redisServer{
//...
//修改计数器,命令修改多少次,该值就增加多少
long dirty;
//上一次执行保存的时间
time_t lastsave;
//...
}
如何检查是否满足保存条件呢?
Redis服务器周期性操作函数serverCron
默认100秒执行一次,该函数对正在运行的服务器进行维护,其中一项工作就是检查save
条件是否满足,满足就执行BGSAVE
命令,伪代码如下:
def serverCron():
#...
# 遍历所有保存条件
for saveparam in server.saveparams:
# 计算离上次保存执行多少秒
save_interval = unixtime_now() - server.lastsave
# 如果数据库修改次数超过条件设置次数
# 并且距离上次保存时间超过满足条件设置的时间
# 那么执行保存操作
if server.dirty >= saveparam.changes and save_interval > saveparam.seconds:
BGSAVE()
# ...
举个例子
目前服务器状态如下:
那么时间1378271101时,也就是301秒之后,那么就满足了第二个条件300秒10次变化,会进行一次BGSAVE
,假如BGSAVE
花费5秒,那么完成后状态服务器如下:
2. AOF(Append Only File)
通过保存服务器执行的写命令实现的
2.1 实现
三部曲:命令追加(append)、文件写入、文件同步。
2.1.1 命令追加
服务器每次执行完一个命令时,以协议格式将命令追加到服务器追昂头aof_buf缓冲区末尾:
struct redisServer{
//...
//AOF缓冲区
sds aof_buf;
//...
}
2.1.2 写入和同步
Redis服务器就是一个事件循环,循环中文件事件负责接收客户端的命令请求,以及向客户端发送命令回复,时间事件复制执行serverCron等函数的运行。
由于服务器在处理文件事件时可能会执行写命令,这些命令就被追加到aof_buf缓冲区里面,所以服务器每次结束一个事件循环之前,都会调用flushAppendOnlyFile函数,考虑是否将aof_buf中的内容写入和保存到aof文件里:
def eventLoop():
while True:
#处理文件事件,接收和回复命令
#处理的命令被追加到aof_buf缓冲区
processFileEvents()
# 处理时间事件
processTimeEvents()
# 考虑是否将aof_buf内容写入AOF中
flushAppendOnlyFile()
flushAppendOnlyFile
函数行为由服务器配置appendfsync
决定(默认值everysec):
appendfsync值 | flushAppendOnlyFile行为 |
---|---|
always | 每个事件循环都将缓冲中内容写入到AOF,并同步,效率最慢,但是最可靠,宕机丢失一个循环命令数据 |
everysec | 每个事件循环都将缓冲中内容写入到AOF,每隔1秒在子进程中对AOF文件进行一次同步,效率快 ,宕机丢失一秒命令数据 |
no | 每个事件循环都将缓冲中内容写入到AOF,何时同步由操作系统决定,所有速度最快,因为会挤压同步数据,所以同步最慢,宕机丢失上次同步之后所有命令 |
2.2 AOF文件载入和数据还原
伪客户端:Redis命令只能在客户端上下文中执行,而载入AOF文件使用的命令来源于AOF而不是网络连接,所以服务器用一个没有网络连接的伪客户端来执行AOF命令,伪客户端执行命令效果和带有网络连接的效果一样
2.3 AOF重写
时间推移-》AOF文件膨胀-》影响宿主机,其中有很多无用臃肿命令,例如:set a 1-->set a 2 --> set a 5 --> set a 1
,最终a=1,其它命令都是无效的。为了解决文件体积问题,Redis提供对AOF文件重写功能,新文件会删除多余的命令,所以体积小很多
2.3.1 实现
AOF重写不需要读取现在AOF文件任何信息,是通过读取数据库现在的数据状态实现的,伪代码:
#遍历数据库
for db in redisServer.db:
#忽略空数据库
if db.is_empty():continue
#写入select命令,指定数据库
f.write_commond("SELECT"+db.id)
#遍历数据库所有的键
for key in db:
#忽略过期键
if key.is_expired():continue
rewtire(key)
#写入完毕,关闭文件
f.close()
2.3.2 后台重写
为了不阻塞服务器处理读写请求,Redis使用子进程进行AOF重写,这么做有两个好处:
- 父进程可以继续处理命令请求
- 子进程带有数据副本,不适用线程可以避免使用锁,保证数据安全
子进程执行重写时,会有命令请求进入服务器,Redis使用重写缓冲区解决此种数据不一致问题:
子进程重写完成后,向父进程发送一个消息,父进程执行以下操作:
- 将AOF重写缓冲区所有内容写入到新的AOF文件中,这是新的AOF文件保存了数据库最新状态
- 对新的AOF改名,原子性性覆盖现有的AOF文件,完成新旧替换
以上就是BGREWRITEAOF
实现原理