Redis单机模式(二):RDB与AOF持久化

本文介绍Redis的两种持久化方式:RDB快照和AOF日志。RDB通过生成数据快照来恢复数据,而AOF则记录执行过的写命令以还原数据库状态。文章详细解释了两种方式的工作原理及优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、RDB持久化:

1. RDB持久化的作用:

Redis数据库中的数据都是保存在内存中的,为了避免服务器进程退出 或者 出现主机下电导致的数据丢失,服务器需要定时的将数据从 内存 存储到 磁盘 中以达到 持久化 的目的。

并且服务器还可以从磁盘中保存的RDB文件中还原数据库的状态。

RDB持久化 既可以手动执行,也可以根据服务器配置选项 定期执行。

总结起来一句话,Redis的RDB持久化功能就是为了避免保存在内存中的数据丢失。


2. RDB文件的创建和载入:

2.1 创建:

有两个 Redis命令 用于 生成RDB文件: SAVEBGSAVE .

redis> SAVE
OK

redis> BGSAVE
Background saving started

区别在于:

(1)SAVE命令 会阻塞 Redis服务器进程,直到 RDB文件 创建完毕,在此期间服务器的进程阻塞,服务器不能处理任何命令请求;

(2)BGSAVE命令 不会阻塞服务器进程,而是 fork 出一个子进程,由 子进程 来负责 创建RDB文件,父进程可以继续正常处理命令请求。在 子进程完成RDB创建后,会发送信号通知父进程。

通过伪代码可以看出二者区别:

SAVE() {
	rdbSave();		//创建RDB文件
}


BGSAVE () { 
	pid = fork();			//创建子进程
	if (pid == 0) {
		rdbSave();
		signal_parent();	//子进程负责创建RDB文件,完成后发送信号通知父进程
	}
	else if (pid>0) {
		handle_request_and_wait_signal();		//父进程继续处理命令请求,并轮询等待子进程的信号
	}
	else {
		handle_fork_error();		//fork()返回负数,创建子进程出错
	}
}


2.2 载入:

Redis在启动阶段会自动检测是否存在RDB文件,如果存在则自动载入。

在此之前,Redis会先检测是否存在AOF文件,因为AOF文件的更新频率比RDB文件的更新频率更高, 所以如果Redis打开了AOF持久化功能,则优先载入AOF文件。


3. 自动间隔性保存:

3.1 设置 saveparam 保存条件:

Redis允许用户通过设置服务器配置文件中的 save选项 (redis.conf),让服务器每隔一段时间自动执行一次 BGSAVE命令 在后台生成RDB文件。

redis.conf 配置文件中 的 默认配置 如下:

save  900 	1			//在900秒之内,对数据库进行至少1次修改
save  300   10			//在300秒之内,对数据库进行至少10次修改
save  60	10000		//在60秒之内, 对数据库进行至少10000次修改

表示 只要满足上面三个条件中的一个,BGSAVE 就会执行。

这段逻辑读起来有点绕,总的思想就是避免 BGSAVE 执行的太过频繁或执行的不及时。


3.2 服务器是如何实现通过save条件进行自动保存的:(在serverCron中执行)

在 redisSever->saveparams 结构体数组中 保存配置文件中的 save选项:

struct redisServer {
	struct saveparam *saveparams;	//记录保存条件的数组
	int				 saveparamslen;	//saveparams数组的元素个数

	long long 		 dirty;			//修改计数器。记录距离上一次成功执行SAVE或BGSAVE后,服务器对数据库状态进行了多少次修改(包括写入、删除、更新等操作
	time_t 			 lastsave;  	//上一次执行保存的时间
}

struct saveparam {
	time_t 			seconds; 		//秒数
	int 			changes;		//修改数
};

在 serverCron 函数中检查保存条件是否满足:

int serverCron() {
	for(j = 0; j < server.saveparamslen; j++) {
		saveparam = server.saveparams[j];
		save_interval = unix_time.now() - server.lastsave;	//距离上次执行BGSAVE的间隔时间

		if(server.dirty >= saveparam.changes && saveparam.seconds) {
			BGSAVE();			//&&
		}
	}
}

4. RDB文件的数据结构:

RDB的总体文件结构:
在这里插入图片描述

带有两个非空数据库的RDB文件示例:
在这里插入图片描述

RDB文件中的数据库结构:
在这里插入图片描述

数据库结构示例:
在这里插入图片描述

RDB文件中的数据库结构示例:
在这里插入图片描述




二、AOF持久化:

5. AOF持久化:

AOF = Append Only File 。

与RDB持久化通过保存数据库中的 键值对 来记录数据库状态不同,AOF持久化是通过保存Redis服务器所执行的 写明令 来记录数据库状态的。


6. AOF持久化的实现:

AOF持久化功能的实现分为 命令追加(append)文件写入文件同步(sync) 三个步骤。


6.1 命令追加:

当AOF持久化功能打开时,服务器每执行完一个写命令后,就会以 协议格式 将被执行的 写明令 追加到服务器的 aof_buf缓冲区 的末尾:

typedef char* sds;

struct redisSever {
	sds		aof_buf;	//AOF缓冲区
};

例如,客户端向服务器发送命令:

redis> SET KEY VALUE
OK

服务器在执行 SET命令 后,会按照 协议格式 将其追加到 aof_buf缓冲区 末尾:

*3\r\n$3\r\nSET\I\n$3\r\nKEY\r\n$5\r \nVALUE\r\n

6.2 文件写入与同步:

6.2.1 操作系统对于文件的写入与同步的处理:

为了提高文件的写入效率,在现在操作系统中,当用户调用 write函数,将一些数据写入到文件时,操作系统通常会将写入数据暂存在一个 内存缓冲区 中,等待缓冲区满 或 超过限定值时,才会真正的将缓冲区中的数据写入磁盘,即 写入 与 同步 是两个分离的动作。

这种做法的好处是提高了效率,缺点是不够安全,如果计算机宕机,则保存在内存缓冲区中的数据将会丢失。

为此,操作系统提供了两个同步函数: fsync 和 fdatasync,用于强制将内存缓冲区中的数据立即写入磁盘。


6.2.2 AOF文件的写入与同步:

在Redis的主循环中,每次处理完 I0事件 和 定时器事件 后,会调用一次 flushAppend0nlyFile 函数,判断是否需要将aof_buf缓冲区 中的数据写入到 AOF文件中。

void aeMain(aeEventLoop *eventLoop)[
	while(!eventLoop->stop) {
		aeProcessEvents(eventLoop, AE_ALL_EVENTS);		//在Redis的主循环中
	}
}

int aeProcessEvents(aeEventLoop *eventLoop, int flags) {
	
	numevents = aeApiPoll(eventLoop);		//处理IO事件
	for(j = 0; j < numevents; j++) {
		if(mask & AE_READABLE)	fe->rfileProc();
		if(mask & AE_WRITABLE)	fe->wfileProc();
	}
	
	processTimeEvents(eventLoop);			//处理定时器事件,在其中调用serverCron函数
}

int serverCron(eventLoop) {
	
	//先判断是否需要执行 RDB BGSAVE(RDB持久化的执行频率会小于 AOF持久化)
	for(j = 0; j < server.saveparamslen; j++) {
		rdbSaveBackGround();
	}

	if(server.aof_flush_postponed_start) {
		flushAppendOnlyFile(0);				//将aof_buf缓冲区中的内容写入到AOF文件
	} 
		
}

与操作提供对write函数的处理类似,flushAppendOnlyFile 函数的 写入 与 同步 也是分离的两步,每次调用 flushAppendOnlyflle函数 时,都会将 aof_buf缓冲区 中的数据写入到 AOF文件(此时AOF
文件仍在内存中),至于是否需要将 AOF文件 从 内存 写入 磁盘(“同步”),则由 redis.conf 配置文件 中的 appendfync配置项决定:

# appendfsync always 		//将AOF文件立即写入磁盘
  appendfsync everysec		//如果距离上次同步AOF文件的时间已经超过一秒钟,则再次对AOF文件同步,并且由一个线程专门执行
# appendf sync no			//从不对AOF文件主动执行同步,何时同步由操作系统决定

其中,always方式的安全性最高,但效率最低; no方式的效率最高,但安全性最差;everysec方式折中考虑,每隔一秒钟对AOF文件同步一次,既兼顾了效率,如果服务器突然宕机也只是丢失一秒钟的写入数据,损失可控。

evenysec方式是Redis的默认同步策略。


6.3 AOF文件的载入与数据还原:

Redis在读取AOF文件还原数据库状态时,会创建一个不带网络连接的伪客户端(因为Redis的命令只能在客户端中执行),从AOF文件中逐条读取写明令并执行,直至将AOF文件中的所有命令处理完毕。


6.4 AOF文件的重写:

  随着服务器运行时间的拉长,执行的写明令会越来越多,AOF文件的体积也会越来越大,为了避免过大的AOF文件对服务器造成影响,需要对AOF文件进行重写。

  虽然这个操作名为“AOF重写”,但实际上它不会对原有的AOF文件进行任何 读取、分析、写入 的操作,而是通过分析当前数据库的状态来另外生成一个AOF文件,直接读取当前数据库中键值对的值,然后执行对应的写操作生成AOF文件。(相当于将重复冗余的读写命令进行删除合并)

  AOF文件的重写操作由 aof_rewrite函数 执行,由于Redis使用单线程处理网络I0事件,而 aof_rewrite函数会进行大量的写入操作,所以为了避免执行AOF重写期间导致线程阻塞无法处理客户端请求,Redis会单独 fork 出一个子进程专门处理AOF重写。

使用子进程而不是子线程的原因是:
子进程带有服务器进程的数据副本,可以在避免使用锁的情况下,保证数据的安全性。

  然而,使用子进程同样引入一个问题,当子进程在进行AOF重写时,服务器主进程还在继续处理客户端的命令请求,而新的命令请求可能会对现有的数据库状态进行修改,从而使得服务器当前的数据库状态和重写后的AOF文件所保存的数据库状态不一致。

  为了解决数据不一致的问题,Redis设置了一个“AOF重写缓冲区”,用于保存在子进程进行AOF重写期间的客户端的写命令请求。 当子进程完成AOF重写工作之后,它会向父进程发送一个信号,父进程在收到信号后会调用一个 信号处理函数,执行以下工作:
(1)将 “AOF重写缓冲区” 中的所有内容写入到 新的 AOF文件中(这时 新的AOF文件所保存的数据库状态与服务器的当前状态一致);
(2)对新的AOF文件进行改名,原子的覆盖现有的AOF文件,完成新旧两个AOF文件的替换。

在整个AOF后台重写过程中,只有调用信号处理函数时会造成服务器进程 阻塞,其他时间服务器都可以正常处理客户端的命令请求,这将AOF重写对服务器的性能影响降到了最低。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值