Redis提供了2个不同形式的持久化策略:
- RDB (redis database)
- AOF (append of file)
这篇文章则记录一下我学习 AOF 过程中的一些笔记,分享给大家。
目录
AOF 模式
1 AOF说明
AOF模式通过保存redis服务器所执行的 写命令 来记录数据库状态。
Redis默认不开启AOF,需要修改默认配置 appendonly 为 yes:
下面举个例子,如果我们对数据库执行了以下写命令:
那么AOF会将服务器执行的SET、SADD、RPUSH三个命令保存在AOF文件中。
这样,AOF通过保存写操作指令来保存和恢复数据库状态。
1.1 何时创建/更新AOF文件
Redis允许通过配置 appendonly yes 开启 AOF,如下图。
Redis允许通过配置 appendfsync 来控制AOF的持久化行为,always是每次执行写操作均写入AOF文件中;everysec是每秒写入一次AOF;no是把何时写入AOF交给操作系统决定,后续会详细说明。
1.2 何时载入和恢复
服务器在启动时,通过载入和执行AOF文件中保存的命令来还原关闭前的数据库状态,以下是服务器载入AOF文件还原数据库的日志:
读取AOF文件并还原的详细步骤如下:
(1)创建一个伪客户端,因为Redis命令只能在客户端上下文执行。
(2)从AOF文件中分析并读取一条写命令。
(3)使用伪客户端执行。
(4)loop步骤2、3直到所有写命令处理完毕。
aof文件名默认为 appendonly.aof,可以通过配置 appendfilename修改:
aof文件保存路径和rdb相同。
1.3 持久化实现
AOF持久化功能可以分为①命令追加、②文件写入、③文件同步三个步骤。
1.3.1 命令追加
当AOF功能打开时,服务器每执行完一个写操作,会以协议格式将被执行的写命令追加到aof_buf缓冲区的末尾,redisServer结构有一个aof_buf属性,用于保存AOF缓冲:
比如客户端向服务器发送以下指令:
则以下内容可以追加到aof_buf缓冲区的末尾:
1.3.2 AOF文件写入和同步
-- 文件的写入和同步
为了提高文件的写入效率,现代操作系统通常有一个内存缓冲区,当用户调用write函数向文件写入数据时,通常将这些内容保存在内存缓冲区里,当缓冲区填满或超过一定的时限后再落盘。
这种做法虽然提高了效率,但是存在数据安全性问题,容易造成数据丢失,因此操作系统提供了fsync和fdatasync同步函数,可以强制立即将缓冲区内容落盘,确保写入数据安全
redis的服务器进程是一个事件循环,其中文件事件负责接收和响应客户端请求,时间事件负责执行类似serverCron这种需要定时执行的函数。
由于处理文件事件可能执行写命令,写入aof_buf,因此每次循环结束都会调用flush AppendOnlyFile函数,考虑是否需要将aof_buf缓冲区内容写入和保存到AOF文件。
flushAppendOnlyFile行为由appendfsync选项决定。
举个例子,假设服务器在处理文件事件期间,执行了3个写命令:
那么aof_buf缓冲区将包含这三个命令的协议内容:
当flushAppendOnlyFile函数被调用,且appendfsync为everysec,距离上次同步AOF已经超过1秒,那么服务器会将缓冲区内容写入到AOF文件,然后同步AOF文件。
2 AOF重写
由于AOF是通过保存写命令记录数据库状态。随着服务器运行,AOF文件会越来越大,如果不加以控制,可能会对Redis服务器甚至宿主机造成很大的影响,且AOF文件越大,还原数据所需的时间就越多。
举个例子,如果客户端执行了下列命令:
那么为了记录list键的状态,AOF文件需要保存4条指令。
为了解决AOF文件体积膨胀的问题,Redis实现了文件重写 rewrite 功能。通过创建新AOF文件来代替现有的AOF文件,但是新AOF不会包括浪费空间的冗余命令,只保存最后能恢复数据库状态的指令,如上面4条指令最后只会保存 RPUSH llist "A" "B" "C""C" "D" "E",4条合并成一条。所以新AOF文件的体积通常比旧AOF文件体积小的多。
2.1 AOF何时重写
redis服务器启动或者上次重写完毕时,服务器会记录此时的AOF大小,设置为base_size,如果服务器当前大小同时满足下面2个条件时,将会进行AOF重写:
(1)base_size(当前大小) >= base_size + base_size * auto-aof-rewrite-percentage(默认100%)
(2)base_size(当前大小) >= auto-aof-rewrite-min-size(默认64MB)
这两个选项可以通过配置修改:
2.2 AOF文件重写的实现
AOF文件重写不是从旧文件读取分析命令然后写入新文件,而是通过读取当前数据库状态实现的,这有点类似于RDB的快照。
比如上面执行了4条命令,AOF重写会直接从数据库中读取键llist的值,然后用 RPUSH llist "A" "B" "C""C" "D" "E" 命令来代理原AOF文件中的4条。
又比如服务器对animals执行了以下命令
AOF文件需要保存上面列出的4条命令,AOF REWRITE则只用 SADD animals "Dog" "Panda" "Tiger" "Lion" "Cat"来代替上面的4条。整个重写过程如下:
事实上,为了避免客户端输入输出缓冲区溢出,重写程序在处理List、Hashtable、Set、Zset这四种可能有多个元素的键时,需要检查键包含的值的元素数量,如果超过了REDIS_AOF_REWRITE_ITEMS_PER_CMD(默认为64)时,则会用多条命令来记录键的值,而不是单条。
比如Set包含的值大于64条时,会如下拆分:
2.3 AOF后台重写
由于AOF重写时,调用aof_rewrite函数会导致线程被长时间阻塞, 导致服务器不可用,REDIS把AOF重写函数放到子进程中执行。这样可以达到两个目的:
(1)重写期间,服务器进程可以继续处理客户端请求。
(2)子进程带有服务器进程的数据副本,使用子进程而不是线程,可以不需要使用锁就能保证数据的安全性。
但是,子进程重写期间,服务器仍然提供服务,处理客户端请求,这个时间内的写操作又会使得数据不一致。
为了解决数据不一致的问题,redis设置了AOF重写缓冲区,当REWRITE期间, REDIS服务器执行一个写命令后,同时将这个写命令追加到AOF缓冲区和AOF重写缓冲区。
这样可以保证:
(1)AOF缓冲区的内容会定期写入并同步到AOF文件,对现有文件的AOF工作会如常进行。
(2)从创建子进程开始,服务器执行的所有写命令都会被记录到AOF重写缓冲区里。
这样即使重写失败,数据也不会丢失。当AOF完成重写工作后,会向父进程(服务器进程)发送一个信号,父进程接收到信号后调用一个信号处理函数,执行以下操作:
(1)将AOF重写缓冲区内容写入到新AOF文件中,此时新AOF文件保存的数据库状态和当前服务器数据库状态一致。
(2)对新的AOF文件改名,替代原AOF文件,完成新旧文件的替换。
在整个重写过程中,父进程只有处理信号函数时会造成阻塞, 其余时间正常提供服务,对服务器造成性能影响降到了最低。
3 AOF的优缺点
优点:
- 备份机制更加稳健,丢失数据概率较低
- 备份文件可读,可以进行误操作处理,如不小心flushdb
缺点:
- 比起RDB占用更多的磁盘空间
- 备份效率低,存在一定的性能压力