《Java后端知识体系系列》之Redis持久化AOF(二)

上一节中知道了AOF是如何进行持久化保存的(保存操作Redis的写命令),还可以通过设置appendfsync来进行不同的持久化行为(always、everysec、no)三个行为不同的持久化结果。

1、AOF文件的载入与数据还原

因为AOF文件里面包含了重建数据库所需要的所有写命令,所以服务器只要读入重新执行一遍AOF文件里面的命令,就可以还原服务器关闭之前的数据库状态。
Redis读取AOF文件并还原数据库的详细步骤如下:
在这里插入图片描述

  • 创建一个不带网络连接的伪客户端(fake client):因为Redis的命令只能再客户端上下文中执行,而载入AOF文件时所使用的命令来源于AOF文件而不是网络连接,所以服务器使用一个没有网络连接的伪客户端来执行AOF文件中所有的写命令。
  • 从AOF文件中分析并读取出一条写命令
  • 使用伪客户端执行被读出的写命令
  • 一直执行步骤2和步骤3,知道AOF文件中所有的写命令都被处理完毕。

当完成以上所有步骤之后,AOF文件所保存的数据都会被还原出来。

2、AOF重写

因为AOF持久化是通过保存被执行的写命令来记录数据的,所以随着服务器运行,AOF文件保存的内容会越来越多,文件的体积也越来越大,如果不加以控制的花,体积过大的AOF文件可能回对Redis服务器或者宿主机造成影响,并且AOF文件越大,使用AOF文件进行数据还原的时间就越多。

为了解决AOF文件体积膨胀的问题,Redis提供了AOF文件重写功能。通过该功能,Redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新旧AOF文件所保存的数据相同,但新AOF文件不会包含任何浪费空间的冗余命令,所以新AOF文件的体积通常比旧AOF文件的体积要小。

2.1 AOF文件重写的实现

虽然Redis将生成新AOF文件替换旧AOF文件功能命令为”重写“,但实际上AOF文件重写并不需要对现有AOF文件进行任何读取、分析或者写入操作,这个功能是通过读取服务器当前的数据库状态来实现的。
例如:Redis对list执行一下命令:

redis>RPUSH list "A" "B"     //["A","B"]
(integer) 2
redis>RPUSH list "C"     //["A","B","C"]
(integer) 3
redis>RPUSH list "D" "E"     //["A","B"]
(integer) 5
redis>LPOP list
"A"
redis>LPOP list
"A"
redis>LPOP list
"B"
redis>RPUSH list "F" "G"
(integer) 5

如果服务器要保存list的数据,那么必须在 AOF中写入六条命令。
如果服务器想要尽量少的命令来记录list的状态,那么最简单高效的办法不是读取现有AOF文件的内容,而是直接从数据库中读取list的值,然后一条RPUSH list "C","D","E","F","G","H" 命令来代替现有AOF文件中的六条命令。这样就可以将保存list所需要的命令减少为一条。
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
#写入SELECT命令,指定数据库号码
f.write_command("SELECT" +db.id)
#遍历数据库中所有的键
for key in db:
#忽略已过期的键
....
#根据键的类型对键进行重写
if key.type == String
...
elif key.type == List
...
elif key.type == Hash
...
elif key.type == Set
...
elif key.type == SortedSet
...
#如果键带有过期时间,那么过期时间也要被重写
if key,have_expire_time()
...
#遍历完毕,关闭文件
f.close
2.2 AOF后台重写

上面说的AOF重写程序aof_rewrite函数可以很好的完成创建一个新AOF文件的任务,但是这个函数会进行大量的写入操作,所以调用这个函数的线程会被长时间阻塞,因为Redis服务器使用单个线程来处理命令请求,所以如果由服务器直接调用aof_rewrite函数,那么重写AOF文件期间,服务器无法处理客户端发来的命令请求。
因此为了解决该问题Redis决定将AOF重写程序放到子线程中执行,这样做可以达到两个目的:

  • 子线程进行AOF重写期间,服务器进程可以继续处理命令请求
  • 子线程带有服务器的数据副本,使用子线程而不是服务器线程,可以避免使用锁的情况下保证数据的安全性。

但是使用子线程也需要解决一个问题就是在子线程进行AOF重写期间,服务器线程继续处理命令请求,新的命令可能会被现有数据库进行修改,会导致当前的数据库和重写的AOF文件保存的数据不一致的问题。
为了结局数据不一致的问题,Redis服务器设置了一个AOF重写缓冲区,这个缓冲区在服务器创建子进程之后开始使用,当Redis服务器执行完一个写命令之后,它会同时将这个写命令发送给AOF缓冲区和AOF重写缓冲区。
所以子线程执行AOF重写期间服务器进程需要执行一下三个工作:

  1. 执行客户端发来的命令
  2. 将执行后的写命令追加到AOF缓冲区
  3. 将执行后的写命令追加到AOF重写缓冲区

这样就保证了:

  • AOF缓冲区的内容会定期被写入和同步到AOF文件,对现有AOF文件的处理工作会一样进行
  • 从创建子线程开始,服务器执行的所有写命令都会被记录到AOF重写缓冲区里面
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值