这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战
Redis 持久化方式除了之前说过的 RDB 持久化以外,还提供了 AOF (Append Only File) 持久化功能。与 RDB 持久化方式通过保存数据库中的键值对来记录数据库状态不同, AOF 持久化是通过保存 Redis 服务器所执行的写命令来记录数据库状态。
在 AOF 文件中,除了用于指定数据库的SELECT命令是服务器自动添加以外,其他的都是我们通过客户端发送的命令。
AOF 持久化的实现
AOF 持久化功能的实现可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤.
命令追加
当打开 AOF 持久化功能,服务器在执行完一个写命令之后,会将被执行的写命令追加到服务器的aof_buf
缓存区的末尾。
struct redisServer {
// ...
sds aof_buf; /* AOF buffer, written before entering the event loop */
// ...
}
AOF 文件的写入和同步
Redis 的服务器进程就是一个事件循环
- 文件事件负责接收客服端的命令请求,向客户端发送命令回复
- 时间事件负责执行需要定时运行的函数
因为服务在执行写命令时,会将内容追加到 aof_buf
缓存区里面,所以在服务器每次结束循环前,会调用 flushAppendOnlyFile()
,考虑是否要将 aof_buf
缓存区中的数据写入和保存到 AOF 文件中。
void flushAppendOnlyFile(int force) {
//...
if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
sync_in_progress = aofFsyncInProgress();
if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) {
/* With this append fsync policy we do background fsyncing.
* If the fsync is still in progress we can try to delay
* the write for a couple of seconds. */
if (sync_in_progress) {
if (server.aof_flush_postponed_start == 0) {
/* No previous write postponing, remember that we are
* postponing the flush and return. */
server.aof_flush_postponed_start = server.unixtime;
return;
} else if (server.unixtime - server.aof_flush_postponed_start < 2) {
/* We were already waiting for fsync to finish, but for less
* than two seconds this is still ok. Postpone again. */
return;
}
/* Otherwise fall trough, and go write since we can't wait
* over two seconds. */
server.aof_delayed_fsync++;
serverLog(LL_NOTICE,"Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.");
}
}
//...
if (nwritten != (ssize_t)sdslen(server.aof_buf)) {
/* Handle the AOF write error. */
if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
/* We can't recover when the fsync policy is ALWAYS since the
* reply for the client is already in the output buffers, and we
* have the contract with the user that on acknowledged write data
* is synced on disk. */
serverLog(LL_WARNING,"Can't recover from AOF write error when the AOF fsync policy is 'always'. Exiting...");
exit(1);
} else {
// ...
}
} else {
/* Successful write(2). If AOF was in error state, restore the
* OK state and log the event. */
if (server.aof_last_write_status == C_ERR) {
serverLog(LL_WARNING,
"AOF write error looks solved, Redis can write again.");
server.aof_last_write_status = C_OK;
}
}
server.aof_current_size += nwritten;
/* Re-use AOF buffer when it is small enough. The maximum comes from the
* arena size of 4k minus some overhead (but is otherwise arbitrary). */
if ((sdslen(server.aof_buf)+sdsavail(server.aof_buf)) < 4000) {
sdsclear(server.aof_buf);
} else {
sdsfree(server.aof_buf);
server.aof_buf = sdsempty();
}
appendfsync 选项的值 | flushAppendOnlyFile 函数的行为 |
---|---|
always | 将 aof_buf 缓存区中的所有内容写入并同步到 AOF 文件 |
everysec | 将 aof_buf 缓存区中的所有内容写入到 AOF 文件,如果上次同步 AOF 文件距离现在超过一秒钟,那么再次对 AOF 进行同步。该判断逻辑有一个线程专门执行 |
no | 将 aof_buf 缓存区中的所有内容写入并同步到 AOF 文件,但并不对 AOF 文件进行同步,何时同步由操作系统决定 |
文件的写入和同步
为了提高文件的写入效率,在现在操作系统中,当用户调用 write 函数,将数据写入到文件的时候,操作系统会将数据暂时写入到内存缓冲区中,等到缓存区的空间被填满或者超过了指定的时限后,才真正的将缓冲区中的数据写入到磁盘中。
系统提供了 fsync 和 fdatasync 两个同步函数,可以强制让操作系统将缓冲区中的数据立即写入到磁盘中,从而保证数据的可靠性。
AOF 文件的载入与数据还原
Redis 读取 AOF 文件并还原数据库状态的详细步骤
- 创建一个不带网络连接的伪客户端
- 从 AOF 文件中分析并读取出一条写命令
- 使用伪客户端执行被读出来的写命令
- 循环执行步骤2和步骤3,直到 AOF 文件中的数据全部读取完毕
AOF 重写
因为 AOF 持久化是通过保存被执行的写命令来记录数据库状态的,所以随着服务器运行时间的流逝,AOF 文件的内容会越来越多,文件体积会越来越大。为了解决 AOF 文件体积膨胀问题,Redis 提供了 AOF 文件重写(rewrite) 功能。
AOF 文件重写的实现
AOF 文件重写不会对现有的 AOF 文件进行任何读取、分析或者写入操作,而是通过读取服务器当前的数据库状态来实现的。
- 从数据库中读取键现在的值
- 用一条命令去记录键值对
为了避免在执行命令时造成客服端输入缓冲区溢出, 重写程序在处理会带有多个元素的键时,会先检查所包含的元素个数,如果超过
AOF_REWRITE_ITEMS_PER_CMD 64
时,改用多条命令进行记录。
AOF 后台重写
Redis 为了不造成服务器无法请求处理,决定将 AOF 重写程序放到子进程里执行。目的有两个:
- 子进程进行 AOF 重写期间,服务器进程可以继续处理命令请求
- 子进程带有服务器进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性
但是会存在一个问题
- 服务器继续处理命令请求,而新的命令可能会对现有数据库状态进行修改,从而使数据不一致
解决方案
为了解决数据不一致的问题,Redis 服务器设置了一个 AOF 重写缓冲区, 这个缓冲区在服务器创建子进程之后开始使用,当 Redis 服务器执行完一个写命令后,会同时发送到 AOF 缓冲区和 AOF 重写缓冲区。
- 执行客户端发来的指令
- 将执行后的写命令追加到 AOF 缓冲区中
- 将执行后的写命令追加到 AOF 重写缓冲区中
当子进程完成 AOF 重写工作之后,他会向父进程发送一个信号,父进程在接到信号后,会调用处理函数,执行以下步骤
- 将 AOF 重写缓冲区中的所有内容写入到新 AOF 文件中,这时新 AOF 文件所保存的数据库状态将和服务器当前的数据库状态保持一致。
- 对新 AOF 文件进行改名,覆盖现有的 AOF 文件。