Redis数据持久化

Redis系列


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

  1. SAVE:服务器进程处理,主动保存
  2. BGSAVE:子进程处理
  3. BGREWRITEAOF:AOF执行命令,子进程进行

SAVEBGSAVE禁止同时进行, 如果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文件载入和数据还原

Created with Raphaël 2.3.0 服务器启动载入程序 创建伪客户端(fake client) 从AOF文件中分析 并读取一条写命令 使用伪客户端执行写命令 确认? 载入完毕 yes no

伪客户端: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重写,这么做有两个好处:

  1. 父进程可以继续处理命令请求
  2. 子进程带有数据副本,不适用线程可以避免使用锁,保证数据安全

子进程执行重写时,会有命令请求进入服务器,Redis使用重写缓冲区解决此种数据不一致问题:
在这里插入图片描述
子进程重写完成后,向父进程发送一个消息,父进程执行以下操作:

  1. 将AOF重写缓冲区所有内容写入到新的AOF文件中,这是新的AOF文件保存了数据库最新状态
  2. 对新的AOF改名,原子性性覆盖现有的AOF文件,完成新旧替换

以上就是BGREWRITEAOF实现原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值