Redis(十五):AOF持久化


AOF持久化与前面提到的RDB持久化不同,RDB持久化是保存数据库中的键值对来记录数据库状态不同,AOF持久化则是通过保存Redis服务器所执行的些命令来记录数据库的状态,保存在AOF文件中,在AOF文件中,除了用于指定数据库的SELECT命令是服务器自动添加的以外,其他都是客户端发送的命令。

AOF持久化的实现

AOF持久化功能的实现分为了命令追加(Append)、文件写入、文件同步(Async)这三个步骤。

命令追加

当AOF持久化功能处于打开状态时,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾,该缓冲池也是RedisServer的一部分

struct redisServer{
    //。。。
    //AOF缓冲区
    sds aof_buf;
    //....
};

举个栗子

set msg 123

当执行完上述这个命令时,就会将这条命令变成协议形式,然后追加到aof_buf缓冲区的末尾(此时还没有写入AOF文件中)

AOF文件的写入与同步

Redis的服务器进程就是一个事件循环(loop),这个循环中的文件事件负责接收客户端的命令请求,以及向客户端发送命令回复,而时间事件则负责执行像serverCron函数这样需要定时运行的函数。

服务器每次结束一个事件循环的之前,会调用flushAppendOnlyFile函数,考虑是否需要将aof_buf缓冲区中的内容写入和保存到AOF文件里面,这个过程伪代码表示如下

def eventLoop(); //事件循环,即整个Redis进程
	while True:
		//处理文件事件,接收用户命令和处理命令后返回结果给用户
		//再处理的时候,aof_buf缓冲池可能会添加新内容
		processFileEvents();
		//处理时间事件
		processTimeEvents();
		//考虑是否将aof_buf缓冲中的内容写入和保存到AOF文件里面
		flushAppendOnlyFile();
	End While
End eventLoop;

flushAppendOnlyFile函数的行为由服务器配置的appendfsync选项(fsync代表同步)的值来决定,各个不同值产生的行为如下面所示

下面是配置文件里面的信息
在这里插入图片描述

appendfsync选项flushAppendOnlyFile函数
always将aof_buf缓冲区中的所有内容写入并同步到AOF文件
everysec(默认值)将aof_buf缓冲区的所有内容写入到AOF文件,如果上次同步AOF文件的事件距离现在超过1秒钟,那么再次对AOF文件进行同步,并且这个同步操作是由一个线程专门负责执行的
no将aof_buf缓冲区的所有内容写入到AOF中,但并不对AOF文件进行同步,何时同步由操作系统来决定
3种方式的比较
  • 当为awlays时,服务器在每个事件循环都会将aof_buf缓冲区的所有内容写入到AOF文件中,并且立刻同步AOF文件(即保存,只有保存了,宕机才不会丢失),所以always效率是最低的,但安全性是最高的,顶多只会丢失一个事件循环里面的命令
  • 当为everysec时,服务器在每个事件循环都会将aof_buf缓冲区的所有内容写入到AOF文件中,此时不会立刻同步,而是看上一次同步的时间是否相隔一秒以上,每隔一秒才会对AOF文件进行同步(保存),效率会比较快,安全性上,最多会丢失1秒钟的数据
  • 当为no时,服务器在每个事件循环都会将aof_buf缓冲区的所有内容写入到AOF文件钟,何时进行同步交由操作系统进行决定,效率最快,但安全性上也最低(完全由操作系统决定损失多少,总的来说会损失上次同步后的所有数据)

即always只要进入了flushAppendOnlyFile就会将缓冲区的内容同步到AOF文件中**(这是最安全的,但也是最慢的,因为访问磁盘次数最多),而everysec则是每隔一秒以上才会进行同步到AOF文件中(这个比较保守,会比always快,但会比always危险一点,假如有一次Loop循环少于1秒完成,那么这个Loop循环的命令会保留在aof_buf缓冲中,不会同步到AOF文件中,假如下一次Loop循环发生宕机,命令来不及写入AOF文件里,那么这个就恢复不了了)**,no选项是最快的,他自己不会决定AOF何时同步aof_buf里面的数据,全部交由操作系统来决定,这种方式是最快,但是最不安全的

AOF文件的载入与数据还原

AOF文件载入

AOF文件里面包含了重建数据库状态所需的所有写命令(只有写),所以服务器只要读入并重新执行一遍AOF文件里面保存的写命令,就可以还原服务器关闭之前的数据库状态。

详细步骤如下:

  1. 首先创建一个不带网络连接的伪客户端(fake client):因为Redis命令只能在客户端那里执行,所以需要客户端才可以执行AOF保存的Redis命令,不带网络连接是因为命令就在本地,不需要网络连接来传输命令
  2. 从AOF文件中分析并且读取出一条写命令
  3. 使用伪客户端去执行读出的写命令
  4. 判断AOF文件里面的所有写命令是否已经被处理完,如果未处理完,返回步骤2,直到命令处理完为止
    在这里插入图片描述
    但服务器首先读入的命令是切换数据库命令,之后才是一系列的写命令

AOF重写

AOF持久化是通过保存被执行的写命令来记录数据库状态的,随着服务器运行时间的增长,AOF文件中的内容也是会越来越多的,文件的体积会越来越大,如果不加以控制,体积过大的AOF文件就很可能对Redis服务器、甚至整个计算机都会造成影响,并且AOF文件的体积越大,数据库还原的时间也是会越来越久。

//redis命令
rpush list 1 2
rpush list 3 4
rpush list 5 6
lpop list
lpop list

假如redis执行了上面的命令,那么AOF文件中,就保留有5条命令,这5条命令完全可以由1条命令去替代,产生了冗余命令的问题

rpush list 1 2 3 4

为了解决体积膨胀问题,Redis提供了AOF文件重写功能(rewrite),该功能会创建一个新的AOF文件来替代现有的AOF文件,新的AOF文件不含有浪费AOF文件空间的冗余命令,所以新的AOF文件的体积会比旧的小很多。

AOF文件重写的实现

虽然文件重写是将新生成的AOF文件替代旧的AOF文件,但其实这整个重写过程不需要对旧的AOF文件进行读取,该功能是通过读取服务器当前数据库的状态去实现的

AOF文件重写实现的原理是从数据库中读取键现在的值,然后用一条命令去记录键值对,代替之前记录这个键值对的多条命令

def aof_rewrite(new_aof_file_name)
	//创建新的AOF文件
	f = create_file(new_aof_file_name)
	//遍历数据库
	for db in redisServer.db;
		//忽略空的数据库
		if db.is_empty();continue
		//数据库不为空,先写入切换命令
		f.write_command("SELECT"+db.id);
		//遍历数据库中的所有键
		for key in db:
			//忽略已经过期的键
			if key.is_expired();continue
			//根据键的类型对键进行重写
			if key.type == String;
				rewrite_string(key);
			else if key.type == List;
				rewrite_list(key);
			else if key.type == Hash;
				rewrite_hash(key);
			else if key.type == Set;
				rewrite_set(key);
			else if key.type == ZSet;
				rewrite_zset(key);
			
			//如果键带有过期时间,过期时间也要被重写进去
			if key.have_expire_time();
				rewrite_expire_time(key)
	//写入完毕
	f.close()
End aof_rewrite;

根据当前键值对的类型去调用不同的方法进行写入,里面的步骤大体都是一样的,比如说String类型,则是调用GET命令获取值,然后再重写SET命令,将重写的SET命令写入AOF文件中,其他类型也是一样,调用命令获取值,然后重写对应命令即可,这样就减少了很多的冗余命令。

注意

在实际中,为了避免执行命令时造成客户端输入缓冲区溢出(AOF文件重写时,会占用输入缓冲区,当前用户的输入缓冲区会受影响),所以在对可以存储多个元素的键时,会检查包含的元素数量,如果数量超过了服务器架构里面的默认值,即redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD常量的值,重写日志的时候将使用多条命令去储存,而不单单使用一条命令

AOF后台重写(BGREWRITEAOF命令)

aof_rewrite函数可以创建一个新AOF文件,但由于不是异步的,会对线程造成阻塞,特别是当AOF写入的命令特别多,次数频繁时,阻塞的现象越严重,因为Redis又是单线程的,所以会在AOF文件重写时,服务器端可能无法处理客户端发来的命令请求。

所以Redis也开启了一个进程来进行AOF文件重写,这样做有以下优点

  • 开启了另一个进程去进行重写,不会对父进程造成影响
  • 由于进程的数据不共享,所以可以在不加锁的情况下,保证数据的安全

但同时也出现了问题,假如使用子进程去进行重写AOF,如果在重写期间,父进程继续处理命令,新的命令可能会对当前数据库进行修改,造成了数据库状态和AOF文件里面的命令状态不一致的问题。

举个栗子

set msg v1
set msg v2
set msg v3
//创建子进程,执行AOF文件重写
//重写未完成,继续修改,因为文件重写,不能利用将命令放回旧文件中,
set msg v4
set msg v5
//AOF文件重写完成,此时才可以进行刷新aof_buffer,里面保存的是set msg v3
//下一步如果不进行修改了,那就不再触发AOF,AOF保存的命令也是错的

所以为了解决这个问题,Redis服务器设置了一个AOF重写缓冲区,这个缓冲区在服务器创建子进程之后就会开始使用,当Redis执行完一个写命令之后,他会同时将这个写命令发送给AOF缓冲区(aof_buffer)和AOF重写缓冲区。

所以,在创建子进程执行AOF命令之后,需要进行三个动作,分别如下

  • 执行客户端发送来的命令
  • 将执行后的写命令追加到AOF缓冲区中(aof_buffer)
  • 将执行后的写命令追加到AOF重写缓冲区中

这样就可以保证

  • AOF缓冲区的内容可以定期被写入和同步到现有AOF文件中,对现有的AOF文件不会造成影响
  • 另一方面就是从创建子进程开始,服务器执行的所有写命令都会被记录到AOF重写缓冲区中,用于追加

当子进程完成AOF重写工作之后,此时会向父进程那里发送一个信号,父进程在接到该信号之后,再调用一个信号处理函数,执行以下工作

  • 将AOF重写缓冲区中的所有内容写入到新创建的AOF文件中,这是新的AOF文件所保存的数据库状态将和服务器当前的数据库状态一致
  • 对新的AOF文件进行改名,原子地覆盖现有的AOF文件,完成新旧两个AOF文件的替换。

总的来说,AOF使用了后台重写缓解了一点阻塞影响,但是在信号处理函数的时候,还是会造成阻塞(不过一般会比后台重写阻塞影响小)
在这里插入图片描述

重点

  • AOF文件通过保存命令的方式来记录服务器的数据库状态
  • AOF文件中的所有命令都会以Redis命令请求协议的格式保存
  • 命令请求会先保存到AOF缓冲区里面,之后再根据appendfsync选项来进行写入和同步到AOF文件中
  • appendfsync选项的不同值对AOF持久化功能的安全性和Redis服务器的效率有影响
  • AOF重写会产生新的AOF文件,这个文件会过滤掉冗余命令,变成一条Redis,不过对可以存储多个元素的键值对,有长度限制,超过长度就会变成多条Redis
  • AOF重写无须读取旧的AOF文件
  • 执行BGREWRITEAOF命令时,如果碰到BGSAVE,不会执行BGSAVE,如果执行BGSAVE期间,碰到BGREWRITEAOF,会延缓执行BGREWRITEAOF(前面RDB提到过)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值