Redis AOF重写的实现原理

1. 操作系统基础

1.1. fork

当我们在一个程序的函数中调用 fork 函数时,fork 函数会创建一个子进程。而原本这个程序对应的进程,就称为这个子进程的父进程。我们可以根据 fork 函数的不同返回值,来编写相应的分支代码,这些分支代码就对应了父进程和子进程各自要执行的逻辑。

fork 函数的不同返回值,其实代表了不同的含义,具体来说:

  • 当返回值小于 0 时,此时表明 fork 函数执行有误;
  • 当返回值等于 0 时,此时,返回值对应的代码分支就会在子进程中运行;
  • 当返回值大于 0 时,此时,返回值对应的代码分支仍然会在父进程中运行。
#include <stdio.h>
#include <unistd.h>
 
int main(int argc, char *argv[]) {
    printf("hello main\n");
    int rv = fork(); //fork函数的返回值
    //返回值小于0,表示fork执行错误
    if (rv < 0) {
        fprintf(stderr, "fork failed\n");
    }
    //返回值等于0,对应子进程执行
    else if (rv == 0) {
        printf("I am child process %d\n", getpid());
    }
    //返回值大于0,对应父进程执行
    else {
        printf("I am parent process of (%d), %d\n", rc, getpid());
    }
    return 0;
}

fork 的时候,父进程的虚拟地址映射着物理内存的实际的物理地址,clone 的时候,并不是在物理地址中直接再复制一份和父进程一样的物理内存块,而是子进程的虚拟地址也直接映射到同一物理内存块中,这就是读时共享。当 父进程/子进程 操作这个物理内存块时(比如修改变量的值),复制该部分的实际物理内存到子进程中,并不是全部复制。这就是写时复制

2.png

1.2. 进程间通信-管道

在父子进程通信时,我们通常都需要依赖操作系统提供的通信机制,而管道(pipe)就是一种用于父子进程间通信的常用机制。具体来说,管道机制在操作系统内核中创建了一块缓冲区,父进程 A 可以打开管道,并往这块缓冲区中写入数据。同时,子进程 B 也可以打开管道,从这块缓冲区中读取数据。

进程每次往管道中写入数据时,只能追加写到缓冲区中当前数据所在的尾部,而进程每次从管道中读取数据时,只能从缓冲区的头部读取数据。其实,管道创建的这块缓冲区就像一个先进先出的队列一样,写数据的进程写到队列尾部,而读数据的进程则从队列头读取。

3.png

管道中的数据在一个时刻只能向一个方向流动,这也就是说,如果父进程 A 往管道中写入了数据,那么此时子进程 B 只能从管道中读取数据。类似的,如果子进程 B 往管道中写入了数据,那么此时父进程 A 只能从管道中读取数据。而如果父子进程间需要同时进行数据传输通信,我们就需要创建两个管道了。

操作系统提供的管道的系统调用 pipe:

int pipe(int pipefd[2]); 

pipe 的参数是一个数组 pipefd,表示的是管道的文件描述符。这是因为进程在往管道中写入或读取数据时,其实是使用 write 或 read 函数的,而 write 和 read 函数需要通过文件描述符才能进行写数据和读数据操作。数组 pipefd 有两个元素 pipefd[0]和 pipefd[1],分别对应了管道的读描述符和写描述符。这也就是说,当进程需要从管道中读数据时,就需要用到 pipefd[0],而往管道中写入数据时,就使用 pipefd[1]。

int main() 
{ 
    int fd[2], nr = 0, nw = 0; 
    char buf[128]; 
    pipe(fd); 
    pid = fork(); 
     
  if(pid == 0) {
      //子进程调用read从fd[0]描述符中读取数据
        printf("child process wait for message\n"); 
        nr = read(fds[0], buf, sizeof(buf)) 
        printf("child process receive %s\n", buf);
  }else{ 
       //父进程调用write往fd[1]描述符中写入数据
        printf("parent process send message\n"); 
        strcpy(buf, "Hello from parent"); 
        nw = write(fd[1], buf, sizeof(buf)); 
        printf("parent process send %d bytes to child.\n", nw); 
    } 
    return 0; 
} 

2. AOF 重写的基本过程

2.1. 创建重写子进程

AOF 重写的函数是 rewriteAppendOnlyFileBackground,它是在aof.c文件中实现的。在这个函数中,会调用 fork 函数创建一个 AOF 重写子进程,然后在子进程中调用 rewriteAppendOnlyFile 函数进行 AOF 文件重写。

int rewriteAppendOnlyFileBackground(void) {
    ......
    //创建管道
    if (aofCreatePipes() != C_OK) return C_ERR;
    ......
    //创建子进程
    if ((childpid = fork()) == 0) {
        ......
        //子进程调用rewriteAppendOnlyFile进行AOF重写
        if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
            ......
        } else {
            ......
        }
    } else {
        ......
        //记录重写子进程的进程号
        server.aof_child_pid = childpid;
        //关闭rehash功能
        updateDictResizePolicy();
        ......
        return C_OK;
    }
    return C_OK; /* unreached */
}

在这个函数中,会调用 aofCreatePipes 函数创建管道,用来实现父子进程间的通信,这个函数稍后再详细介绍。

  • 子进程:调用 rewriteAppendOnlyFile 函数来完成 AOF 日志文件的重写。
  • 父进程:记录子进程进程号,禁止在 AOF 重写期间进行 rehash 操作。这是因为 rehash 操作会带来较多的数据移动操作,对于 AOF 重写子进程来说,这就意味着父进程中的内存修改会比较多。因此,就需要执行更多的写时aa完成 AOF 文件的写入,这就会给 Redis 系统的性能造成负面影响。

rewriteAppendOnlyFile 函数是在 aof.c 文件中实现的。它主要会调用 rewriteAppendOnlyFileRio 函数(在 aof.c 文件中)来执行 AOF 重写操作:

int rewriteAppendOnlyFile(char *filename) {
    ......
    if (rewriteAppendOnlyFileRio(&aof) == C_ERR) goto werr;
    ......

}

关于 rewriteAppendOnlyFile 函数更多的操作,稍后会在介绍进程通信中详细介绍。

rewriteAppendOnlyFileRio 具体来说,就是遍历 Redis server 的每一个数据库,把其中的每个键值对读取出来,然后记录该键值对类型对应的插入命令,以及键值对本身的内容。比如,如果读取的是一个 String 类型的键值对,那么 rewriteAppendOnlyFileRio 函数,就会记录 SET 命令和键值对本身内容;而如果读取的是 Set 类型键值对,那么它会记录 SADD 命令和键值对内容。这样一来,当需要恢复 Redis 数据库时,我们重新执行一遍 AOF 重写日志中记录的命令操作,就可以依次插入所有键值对了。

int rewriteAppendOnlyFileRio(rio *aof) {
    ......
    for (j = 0; j < server.dbnum; j++) {
        ......
        /* Iterate this DB writing every entry */
        while((de = dictNext(di)) != NULL) {
            /* Save the key and associated value */
            if (o->type == OBJ_STRING) {
                /* Emit a SET command */
                char cmd[]="*3\r\n$3\r\nSET\r\n";
                if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) goto werr;
                /* Key and value */
                if (rioWriteBulkObject(aof,&key) == 0) goto werr;
                if (rioWriteBulkObject(aof,o) == 0) goto werr;
            } else if ......
        }
        ......
    }
    ......

}

2.2. 同步写操作

AOF 重写子进程在执行重写操作期间,父进程仍然在接收客户端的写操作,而子进程需要把这些写操作写到重写日志里面。这就用到了刚才介绍的管道机制,在AOF 重写的函数是 rewriteAppendOnlyFileBackground 最开始,调用了aofCreatePipes 函数来创建管道,下面来看下这个函数:

int aofCreatePipes(void) {
    int fds[6] = {-1, -1, -1, -1, -1, -1};
    int j;
    //1. 创建三个管道,每个管道对应两个FD,一个读,一个写
    if (pipe(fds) == -1) goto error; /* parent -> children data. */
    if (pipe(fds+2) == -1) goto error; /* children -> parent ack. */
    if (pipe(fds+4) == -1) goto error; /* parent -> children ack. */
    /* Parent -> children data is non blocking. */
    //第一和第二个描述符设置为非阻塞
    if (anetNonBlock(NULL,fds[0]) != ANET_OK) goto error;
    if (anetNonBlock(NULL,fds[1]) != ANET_OK) goto error;
    //注册读事件的监听,当 fds[2] 上有读事件发生时,会执行 aofChildPipeReadable 函数
    if (aeCreateFileEvent(server.el, fds[2], AE_READABLE, aofChildPipeReadable, NULL) == AE_ERR) goto error;
    //设置全局变量,让子进程也能访问到这些管道
    server.aof_pipe_write_data_to_child = fds[1];
    server.aof_pipe_read_data_from_parent = fds[0];
    server.aof_pipe_write_ack_to_parent = fds[3];
    server.aof_pipe_read_ack_from_child = fds[2];
    server.aof_pipe_write_ack_to_child = fds[5];
    server.aof_pipe_read_ack_from_parent = fds[4];
    server.aof_stop_sending_diff = 0;
    return C_OK;
    ......
}

从 server 变量的成员变量名中,看到 aofCreatePipes 函数创建的三个管道,以及它们各自的用途:

  • fds[0]和 fds[1]:对应了主进程和重写子进程间用于传递操作命令的管道,它们分别对应读描述符和写描述符。
  • fds[2]和 fds[3]:对应了重写子进程向父进程发送 ACK 信息的管道,它们分别对应读描述符和写描述符。
  • fds[4]和 fds[5]:对应了父进程向重写子进程发送 ACK 信息的管道,它们分别对应读描述符和写描述符。

Redis 主进程在收到写操作之后,和 AOF 重写子进程的交互流程如下图所示:


4.png

下面依次来分析图中的流程:

  1. 在 AOF 重写期间,父进程接收到客户端写操作,会调用 feedAppendOnlyFile(在 aof.c 文件中) 函数来将接收到的写操作写入 AOF 日志,而在 feedAppendOnlyFile 函数的最后,会判断当前是否有 AOF 重写子进程在运行。如果有的话,它就会调用 aofRewriteBufferAppend 函数(在 aof.c 文件中)来写操作命令管道,如下所示:

    void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
     ......
     /* If a background append only file rewriting is in progress we want to
         * accumulate the differences between the child DB and the current one
         * in a buffer, so that when the child process will do its work we
         * can append the differences to the new append only file. */
        if (server.aof_child_pid != -1)
            aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));
    
        sdsfree(buf);
    
    }
    
  2. aofRewriteBufferAppend 函数的作用是将字节数组参数 buf,追加写到全局变量 server 的 aof_rewrite_buf_blocks 这个列表中

    当 aofRewriteBufferAppend 函数将命令操作记录到 aof_rewrite_buf_blocks 列表中之后,它还会检查 aof_pipe_write_data_to_child 管道描述符上是否注册了写事件,这个管道描述符就对应了我刚才给你介绍的 fds[1]。如果没有注册写事件,那么 aofRewriteBufferAppend 函数就会调用 aeCreateFileEvent 函数,注册一个写事件,这个写事件会监听 aof_pipe_write_data_to_child 这个管道描述符,也就是主进程和重写子进程间的操作命令传输管道。

    当这个管道可以写入数据时,写事件对应的回调函数 aofChildWriteDiffData(在 aof.c 文件中)就会被调用执行。这个过程你可以参考下面的代码:

    /* Append data to the AOF rewrite buffer, allocating new blocks if needed. */
    void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {
        //从 aof_rewrite_buf_blocks 列表中取出最后一个 aofrwblock 类型的数据块
        listNode *ln = listLast(server.aof_rewrite_buf_blocks);
        //aofrwblock 记录了重写AOF子进程过程中,主进程接收到的命令
        aofrwblock *block = ln ? ln->value : NULL;
    
        while(len) {
         //把传入的 char *s 写到 aofrwblock 数据块中
            ......
        }
    
        /* Install a file event to send data to the rewrite child if there is
         * not one already. */
         //检查aof_pipe_write_data_to_child描述符上是否有事件
        if (aeGetFileEvents(server.el,server.aof_pipe_write_data_to_child) == 0) {
            //如果没有注册事件,那么注册一个写事件,回调函数是aofChildWriteDiffData
            aeCreateFileEvent(server.el, server.aof_pipe_write_data_to_child,
                AE_WRITABLE, aofChildWriteDiffData, NULL);
        }
    }
    
    typedef struct aofrwblock {
        //buf数组已用空间和剩余可用空间
        unsigned long used, free;
        //宏定义AOF_RW_BUF_BLOCK_SIZE默认为10MB
        char buf[AOF_RW_BUF_BLOCK_SIZE];
    } aofrwblock;
    

    而 aofChildWriteDiffData 函数负责把 server.aof_rewrite_buf_blocks 列表块中的数据取出来,并写到 aof_pipe_write_data_to_child 管道中:

    /* Event handler used to send data to the child process doing the AOF
     * rewrite. We send pieces of our AOF differences buffer so that the final
     * write when the child finishes the rewrite will be small. */
    void aofChildWriteDiffData(aeEventLoop *el, int fd, void *privdata, int mask) {
        listNode *ln;
        aofrwblock *block;
        ......
    
        while(1) {
            //从aof_rewrite_buf_blocks列表中取出数据块
            ln = listFirst(server.aof_rewrite_buf_blocks);
            block = ln ? ln->value : NULL;
            if (server.aof_stop_sending_diff || !block) {
                aeDeleteFileEvent(server.el,server.aof_pipe_write_data_to_child,
                                  AE_WRITABLE);
                return;
            }
            if (block->used > 0) {
                //调用write将数据块写入主进程和重写子进程间的管道
                nwritten = write(server.aof_pipe_write_data_to_child,
                                 block->buf,block->used);
                ......
            }
        }
    }
    
  3. 子进程从管道中读取父进程写操作的逻辑是在 aofReadDiffFromParent 函数中,它会将读取的操作命令追加到全局变量 server 的 aof_child_diff 字符串中。而在 AOF 重写函数 rewriteAppendOnlyFile 的执行过程最后,aof_child_diff 字符串会被写入 AOF 重写日志文件,以便我们在使用 AOF 重写日志时,能尽可能地恢复重写期间收到的操作。

    /* This function is called by the child rewriting the AOF file to read
     * the difference accumulated from the parent into a buffer, that is
     * concatenated(连接) at the end of the rewrite. */
    ssize_t aofReadDiffFromParent(void) {
        //管道默认的缓冲区大小
        char buf[65536]; /* Default pipe buffer size on most Linux systems. */
        ssize_t nread, total = 0;
        //调用read函数从aof_pipe_read_data_from_parent中读取数据
        while ((nread =
                read(server.aof_pipe_read_data_from_parent,buf,sizeof(buf))) > 0) {
            server.aof_child_diff = sdscatlen(server.aof_child_diff,buf,nread);
            total += nread;
        }
        return total;
    }
    

    而 aofReadDiffFromParent 函数被调用的时机有下面三种:

    • rewriteAppendOnlyFileRio 函数:这个函数是由重写子进程执行的,它负责遍历 Redis 每个数据库,生成 AOF 重写日志,在这个过程中,它会不时地调用 aofReadDiffFromParent 函数。
    • rewriteAppendOnlyFile 函数:这个函数是重写日志的主体函数,也是由重写子进程执行的,它本身会调用 rewriteAppendOnlyFileRio 函数。此外,它在调用完 rewriteAppendOnlyFileRio 函数后,还会多次调用 aofReadDiffFromParent 函数,以尽可能多地读取主进程在重写日志期间收到的操作命令。
    • rdbSaveRio 函数:这个函数是创建 RDB 文件的主体函数。当我们使用 AOF 和 RDB 混合持久化机制时,这个函数也会调用 aofReadDiffFromParent 函数。
  4. 子进程向父进程写入"!",让主进程停止写入新的操作。这一部分逻辑是在重写子进程的主体函数 rewriteAppendOnlyFile 中,实现如下所示:

    int rewriteAppendOnlyFile(char *filename) {
     /* Ask the master to stop sending diffs. */
        //这就是重写子进程向主进程发送 ACK 信号,让主进程停止发送收到的新写操作
        if (write(server.aof_pipe_write_ack_to_parent,"!",1) != 1) goto werr;
    }
    
  5. 父进程读取子进程 ACK 管道中的 "!"。在创建管道的时候,会调用事件驱动框架的创建事件函数,注册读取函数 aofChildPipeReadable。并判断写入的字符是否是"!"

    /* This event handler is called when the AOF rewriting child sends us a
     * single '!' char to signal we should stop sending buffer diffs. The
     * parent sends a '!' as well to acknowledge. */
    void aofChildPipeReadable(aeEventLoop *el, int fd, void *privdata, int mask) {
     ......
     if (read(fd,&byte,1) == 1 && byte == '!') {
         ......
     }
    
    }
    
  6. 父进程向子进程写入"!",确认收到子进程的 ACK。同样也是在 aofChildPipeReadable 函数中

     if (write(server.aof_pipe_write_ack_to_child,"!",1) != 1) {
                /* If we can't send the ack, inform the user, but don't try again
                 * since in the other side the children will use a timeout if the
                 * kernel can't buffer our write, or, the children was
                 * terminated. */
                serverLog(LL_WARNING,"Can't send ACK to AOF child: %s",
                    strerror(errno));
         }
    
  7. 子进程同步读取父进程的确认ACK。在重写子进程主体函数 rewriteAppendOnlyFile 中,调用 syncRead 函数同步读取父进程的确认ACK。并设置了超时时间,看注释上说是10s,但是代码里面传参好像是5s,这个先忽略吧。如果经过超时时间未读取到父进程的ACK,就退出子进程。

    /* We read the ACK from the server using a 10 seconds timeout. Normally
         * it should reply ASAP, but just in case we lose its reply, we are sure
         * the child will eventually get terminated. */
        //从 aof_pipe_read_ack_from_parent 管道描述符上,读取主进程发送给它的 ACK 信息
        if (syncRead(server.aof_pipe_read_ack_from_parent,&byte,1,5000) != 1 ||
            byte != '!') goto werr;
    

2.3. 触发时机

前面大致讨论了一下 AOF 重写的执行流程,接下来说一下 AOF 重写在什么时候会被触发。

AOF 重写的函数 rewriteAppendOnlyFileBackground 一共会在三个地方被调用:

  • bgrewriteaofCommand 函数:对应了我们在 Redis server 上执行 bgrewriteaof 命令,也就是说,我们手动触发了 AOF rewrite 的执行。
  • startAppendOnly 函数:该函数是在执行开启 AOF 功能的 config 命令和 主从节点的复制过程中被调用。简单来说,就是当主从节点在进行复制时,如果从节点的 AOF 选项被打开,那么在加载解析 RDB 文件时,AOF 选项就会被关闭。然后,无论从节点是否成功加载了 RDB 文件,都会恢复被关闭的 AOF 功能,进而触发 AOF 重写。
  • serverCron 函数:serverCron 函数是会被周期性执行的,它在执行的过程中,会做两次判断来决定是否执行 AOF 重写。

这里重点介绍下第三个函数 serverCron 函数,他是在 server.c 文件中,redis一启动,就会注册一个时间事件,该事件1ms后会被触发,触发后的回调函数就是这个 serverCron 函数,然后这个函数会每100ms(默认,取决于 hz 配置)执行一次。

首先进行三个判断,如果没有RDB子进程,也没有AOF重写子进程,并且AOF重写被设置为待调度执行,那么调用rewriteAppendOnlyFileBackground函数进行AOF重写。接下来看下 server.aof_rewrite_scheduled 什么时候会被设置未1。

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    ......
    /* Start a scheduled AOF rewrite if this was requested by the user while
     * a BGSAVE was in progress. */
     //如果没有RDB子进程,也没有AOF重写子进程,并且AOF重写被设置为待调度执行,
     //那么调用rewriteAppendOnlyFileBackground函数进行AOF重写
    if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 &&
        server.aof_rewrite_scheduled)
    {
        rewriteAppendOnlyFileBackground();
    }
    .......
}

同样在 serverCron 函数中,会接下来进行一个判断:

        /* Trigger an AOF rewrite if needed. */
        //如果AOF功能启用、没有RDB子进程和AOF重写子进程在执行、
        //AOF文件大小比例设定了阈值,以及AOF文件大小绝对值超出了阈值,那么,进一步判断AOF文件大小比例是否超出阈值
        if (server.aof_state == AOF_ON &&
            server.rdb_child_pid == -1 &&
            server.aof_child_pid == -1 &&
            server.aof_rewrite_perc &&
            server.aof_current_size > server.aof_rewrite_min_size)
        {
            //计算AOF文件当前大小超出基础大小的比例
            long long base = server.aof_rewrite_base_size ?
                server.aof_rewrite_base_size : 1;
            long long growth = (server.aof_current_size*100/base) - 100;

            //如果AOF文件当前大小超出基础大小的比例已经超出预设阈值,那么执行AOF重写
            if (growth >= server.aof_rewrite_perc) {
                serverLog(LL_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth",growth);
                rewriteAppendOnlyFileBackground();
            }
        }

那么,从这里的代码中,你会看到,为了避免 AOF 文件过大导致占用过多的磁盘空间,以及增加恢复时长,你其实可以通过设置 redis.conf 文件中的以下两个阈值,来让 Redis server 自动重写 AOF 文件。

  • auto-aof-rewrite-percentage:AOF 文件大小超出基础大小的比例,默认值为 100%,即超出 1 倍大小。
  • auto-aof-rewrite-min-size:AOF 文件大小绝对值的最小值,默认为 64MB。

在这里有一个比较关键的变量 server.aof_rewrite_base_size,在aof.c文件的 backgroundRewriteDoneHandler 函数中,会把 server.aof_rewrite_base_size 设置为 server.aof_current_size。从命名上看应该是在 AOF 重写操作完成后,会调这个函数。但是我的编辑器有问题,看不出来是哪里调用了他,这个问题后面在公司电脑上看看能不能解决。

void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
        if (server.aof_fd == -1) {
            /* AOF disabled, we don't need to set the AOF file descriptor
             * to this new file, so we can close it. */
            close(newfd);
        } else {
            /* AOF enabled, replace the old fd with the new one. */
            ......
            server.aof_rewrite_base_size = server.aof_current_size;
            ......
        }

3. 潜在风险

AOF 重写机制可能引起 Redis 主线程阻塞的原因:

  1. Redis 主线程 fork 创建 bgrewriteaof 子进程时,内核需要创建用于管理子进程的相关数据结构,这些数据结构在操作系统中通常叫作进程控制块(Process Control Block,简称为 PCB)。内核要把主线程的 PCB 内容拷贝给子进程。这个创建和拷贝过程由内核执行,是会阻塞主线程的。而且,在拷贝过程中,子进程要拷贝父进程的页表,这个过程的耗时和 Redis 实例的内存大小有关。如果 Redis 实例内存大,页表就会大,fork 执行时间就会长,这就会给主线程带来阻塞风险。

  2. bgrewriteaof 子进程会和主线程共享内存。当主线程收到新写或修改的操作时,主线程会申请新的内存空间,用来保存新写或修改的数据,如果操作的是 bigkey,也就是数据量大的集合类型数据,那么,主线程会因为申请大空间而面临阻塞风险。因为操作系统在分配内存空间时,有查找和锁的开销,这就会导致阻塞。

  3. AOF 重写会对磁盘进行大量 IO 操作,同时,fsync 又需要等到数据写到磁盘后才能返回,所以,当 AOF 重写的压力比较大时,就会导致 fsync 被阻塞。虽然 fsync 是由后台子线程负责执行的,但是,主线程会监控 fsync 的执行进度。

    当主线程使用后台子线程执行了一次 fsync,需要再次把新接收的操作记录写回磁盘时,如果主线程发现上一次的 fsync 还没有执行完,那么它就会阻塞。所以,如果后台子线程执行的 fsync 频繁阻塞的话(比如 AOF 重写占用了大量的磁盘 IO 带宽),主线程也会阻塞,导致 Redis 性能变慢。

    1.jpg

  4. 如果业务应用对延迟非常敏感,但同时允许一定量的数据丢失,那么,可以把配置项 no-appendfsync-on-rewrite 设置为 yes,如下所示:

no-appendfsync-on-rewrite yes

这个配置项设置为 yes 时,表示在 AOF 重写时,不进行 fsync 操作。也就是说,Redis 实例把写命令写到内存后,不调用后台线程进行 fsync 操作,就可以直接返回了。当然,如果此时实例发生宕机,就会导致数据丢失。反之,如果这个配置项设置为 no(也是默认配置),在 AOF 重写时,Redis 实例仍然会调用后台线程进行 fsync 操作,这就会给实例带来阻塞。

参考资料:

  1. 极客时间专栏《Redis源码剖析与实战》.蒋德钧.2021
  2. 极客时间专栏《Redis核心技术与实战》.蒋德钧.2020
  3. 极客时间专栏《趣谈Linux操作系统》.刘超.2019
  4. Redis 5.0.14 源码:https://github.com/redis/redis/tree/5.0
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容