Redis持久化--AOF

  除了RDB持久化之外,Redis还提供了AOF(Append Only File)持久化功能。与RDB持久化通过保存数据库中键值对来保存数据库的状态不同,AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库的状态。被写入AOF文件的所有命令都是以Redis的命令请求协议格式保存的,该格式是一种纯本文的格式,所以可以通过直接打开AOF文件,观察里面的类容。

1 AOF持久化的实现

  AOF持久化功能的实现可以分为:命令追加(append),文件写入(write),文件同步(sync)三个步骤。

1.1 命令追加

AOF持久化需要将所有写命令记录在文件中来保存服务器状态,而文件写入操作效率比较低,如果每执行一条写命令都要写一次AOF文件无疑是低效的。为了提高效率,Redis提供了一个中间层 – AOF缓冲区,也就是说当Redis执行一条写命令后,先将该命令追加到AOF缓冲区中,在以后的某个时刻再将AOF缓冲区中的内容同步到文件中。当AOF持久化功能处于打开状态时,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾:

struct redisServer {
    ...
    sds aof_buf;/* AOF buffer, written before entering the event loop */
}  

服务器执行完写命令,调用propagate进行命令追加。

//进行命令追加
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
               int flags)
{
    if (server.aof_state != AOF_OFF && flags & PROPAGATE_AOF)
        feedAppendOnlyFile(cmd,dbid,argv,argc);
}

将命令追加到缓冲区中的操作由feedAppendOnlyFile函数实现,如果后台正在执行AOF文件后台重写操作,该命令还会被追加到AOF重写缓存中。

void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
    sds buf = sdsempty();
    robj *tmpargv[3];

    /* The DB this command was targeting is not the same as the last command
     * we appended. To issue a SELECT command is needed. */
    // 如果当前命令涉及的数据库与server.aof_selected_db指明的数据库不一致,需要加入SELECT命令显式设置
    if (dictid != server.aof_selected_db) {
        char seldb[64];

        snprintf(seldb,sizeof(seldb),"%d",dictid);
        buf = sdscatprintf(buf,"*2\r\n$6\r\nSELECT\r\n$%lu\r\n%s\r\n",
            (unsigned long)strlen(seldb),seldb);
        server.aof_selected_db = dictid;
    }

    // 处理EXPIRE, SETEX, EXPIREAT命令
    if (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
        cmd->proc == expireatCommand) {
        /* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */
        // 将EXPIRE/PEXPIRE/EXPIREAT命令都转换为PEXPIREAT命令
        buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);
    }
    // 处理SETEX、PSETEX命令
    else if (cmd->proc == setexCommand || cmd->proc == psetexCommand) {
        /* Translate SETEX/PSETEX to SET and PEXPIREAT */
        // 将SETEX/PSETEX命令转换为SET命令和PEXPIREAT命令
        tmpargv[0] = createStringObject("SET",3);
        tmpargv[1] = argv[1];
        tmpargv[2] = argv[3];
        buf = catAppendOnlyGenericCommand(buf,3,tmpargv);
        decrRefCount(tmpargv[0]);
        buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);
    }
    // 其它命令使用catAppendOnlyGenericCommand()函数处理
    else {
        /* All the other commands don‘t need translation or need the
         * same translation already operated in the command vector
         * for the replication itself. */
        // 所有其它命令并不需要转换操作或者已经完成转换,采用此函数将将写命令转化为命令协议格式的字符串
        buf = catAppendOnlyGenericCommand(buf,argc,argv);
    }

    /* Append to the AOF buffer. This will be flushed on disk just before
     * of re-entering the event loop, so before the client will get a
     * positive reply about the operation performed. */
    if (server.aof_state == REDIS_AOF_ON)
        server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf));

    /* 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. */
    // 如果后台正在执行AOF文件重写操作(即BGREWRITEAOF命令),为了记录当前正在重写的AOF文件和当前数据库的
    // 差异信息,我们还需要将重构后的命令追加到AOF重写缓存中。
    if (server.aof_child_pid != -1)
        aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));

    sdsfree(buf);
}

aof.c文件中的catAppendOnlyGenericCommand函数提供了根据传入命令和该命令的参数将其构造成满足AOF文件格式的字符串的功能。

sds catAppendOnlyGenericCommand(sds dst, int argc, robj **argv) {
    char buf[32];
    int len, j;
    robj *o;

    // 构建格式为“*<count>\r\n"格式的字符串,<count>为命令参数个数
    buf[0] = ‘*‘;
    len = 1+ll2string(buf+1,sizeof(buf)-1,argc);
    buf[len++] = ‘\r‘;
    buf[len++] = ‘\n‘;
    dst = sdscatlen(dst,buf,len);

    // 重建命令,每个item的格式为“$<len>\r\n<content>\r\n”,其中<len>指明<content>的字符长度,<content>为参数内容
    for (j = 0; j < argc; j++) {
        o = getDecodedObject(argv[j]);
        buf[0] = ‘$‘;
        len = 1+ll2string(buf+1,sizeof(buf)-1,sdslen(o->ptr));
        buf[len++] = ‘\r‘;
        buf[len++] = ‘\n‘;
        dst = sdscatlen(dst,buf,len);
        dst = sdscatlen(dst,o->ptr,sdslen(o->ptr));
        dst = sdscatlen(dst,"\r\n",2);
        decrRefCount(o);
    }
    // 返回重建后的命令内容
    return dst;
}

1.2 文件写入(write)和同步(sync)

  在上面的介绍中,我们调用feedAppendOnlyFile函数只是把命令追加到了AOF缓冲区server.aof_buf中,并没有写入到磁盘文件中。

  在现代操作系统中,当用户将数据写入一个文件中时,为了提高效率,操作系统会先利用一个缓冲区来存放写入的内容,直到这个缓冲区满了或者超过指定的时间后才真正将缓冲区中的内容写入到磁盘文件中。为了强制让操作系统将缓冲区中的数据写入磁盘,一般可以通过fsync()函数来强制写入到磁盘中。而fsync()函数的调用频率就是我们这一小节要介绍的“同步策略”。

  在处理文件事件时(写命令),命令被追加到aof_buf中;然后在处理时间事件时,serverCron函数会调用flushAppendOnlyFile函数进行文件的写入和同步。Redis可以通过配置redis.conf文件中的flush选项来指定AOF同步策略,主要支持以下三种同步策略:

  • AOF_FSYNC_NO

    在该模式下,Redis服务器在每个事件循环都将AOF缓冲区server.aof_buf中的数据写入AOF文件中,但不执行同步fsync方法,由操作系统决定何时同步。该模式速度最快(无需执行同步操作)但也最不安全(如果机器崩溃将丢失上次同步后的所有数据)。

  • AOF_FSYNC_ALWAYS

    在该模式下,Redis服务器在每个事件循环都将AOF缓冲区server.aof_buf中的数据写入AOF文件中,且执行一次AOF文件同步操作。该模式速度最慢(每个事件循环都要执行同步操作)但也最安全(如果机器崩溃只丢失当前事件循环中处理的新数据)。

  • AOF_FSYNC_EVERYSEC

    在该模式下,Redis服务器在每个事件循环都将AOF缓冲区server.aof_buf中的数据写入AOF文件中,且每秒执行一次AOF文件同步操作。该模式效率和安全性(如果机器崩溃只丢失前一秒处理的新数据)比较适中,是Redis的默认同步策略。

void flushAppendOnlyFile(int force) {
    if (sdslen(server.aof_buf) == 0) return;
    if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
        sync_in_progress = bioPendingJobsOfType(BIO_AOF_FSYNC) != 0;
    if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) {
        if (sync_in_progress) {
            if (server.aof_flush_postponed_start == 0) {
                server.aof_flush_postponed_start = server.unixtime;
                return;
            } else if (server.unixtime - server.aof_flush_postponed_start < 2) {
                return;
            }
            server.aof_delayed_fsync++;
        }
    }
    //将aof_buf中的内容写入到aof文件
    nwritten = write(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
    server.aof_flush_postponed_start = 0;
    ……
    server.aof_current_size += nwritten;
    if ((sdslen(server.aof_buf)+sdsavail(server.aof_buf)) < 4000) {
        sdsclear(server.aof_buf);
    } else {
        sdsfree(server.aof_buf);
        server.aof_buf = sdsempty();
    }
    /*Don‘t fsync if no-appendfsync-on-rewrite is set to yes and there are children doing I/O in the background. */
    if (server.aof_no_fsync_on_rewrite &&
        (server.aof_child_pid != -1 || server.rdb_child_pid != -1))
            return;
    /* appendfsync为always */
    if (server.aof_fsync == AOF_FSYNC_ALWAYS) {/
        aof_fsync(server.aof_fd); //同步aof文件
        server.aof_last_fsync = server.unixtime;//记录同步时间
    } else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&
                server.unixtime > server.aof_last_fsync)) {
        /* appendfsync为EVERYSEC*/
        if (!sync_in_progress) aof_background_fsync(server.aof_fd);
        server.aof_last_fsync = server.unixtime;
    }
}  

// 在另一个线程中,对给定的描述符 fd (指向 AOF 文件)执行一个后台 fsync() 操作。
void aof_background_fsync(int fd) {
    bioCreateBackgroundJob(BIO_AOF_FSYNC,(void*)(long)fd,NULL,NULL);
}  

2 AOF文件的载入与数据还原

  数据还原就是将AOF文件中保存的命令解析并执行,这样就可以将数据库还原为原来的状态。因为在Redis中,命令必须由redisClient实例来执行,所以为了加载AOF文件需要创建一个伪Redis客户端。创建了伪Redis客户端后,执行数据还原的过程就是从AOF文件中读取命令并交给伪Redis客户端执行的过程。数据还原的功能由aof.c文件中的loadAppendOnlyFile函数完成。

3 AOF重写

  由于aof是通过不断追加写命令来记录数据库状态,所以服务器执行比较久之后,aof文件中的内容会越来越多,磁盘占有量越来越大,同时也是使通过过aof文件还原数据库的需要的时间也变得很久。所以就需要通过读取服务器当前的数据库状态来重写新的aof文件。新的AOF文件不会包含任何浪费空间的冗余命令,所以会比旧的AOF文件小很多。

  由于AOF重写是会进行大量写入操作,势必为长时间阻塞主进程,因此redis把重写程序放到子进程执行。

这样做有两点好处

1)子进程重写期间,主进程可以继续处理命令。

2)子进程带有主进程的数据副本,这样就可以避免与主进程竞争db->dict,这是线程实现不了的。

  重写期间,主进程继续处理命令,对数据库状态进行修改,这样使得当前的数据库状态与重写的AOF文件所保存的数据库状态不一致。因此,redis设置了AOF重写缓冲区,在创建子进程后,主进程每执行一个写命令都会写到重写缓冲区。在子进程完成重写后,主进程会将AOF重写缓冲区的数据写入到重写的AOF文件,保证数据状态的一致。

  在子进程执行AOF重写期间,服务器进程需要执行以下三个操作:

  (1)执行客户端发送过来的命令;

  (2)将执行的命令追加到AOF缓冲区;

  (3)将执行的命令追加到AOF重写缓冲期;

  这样就可以保证:

  • AOF缓冲区的类容会定期写入和同步到AOF文件,即对现有的AOF文件的工作会正常进行
  • 从创建子进程开始,服务器执行的所有写命令会被记录到AOF重写缓冲区里面。

  当子进程执行完毕后,会向父进程发送一个信号。父进程收到信号后,将执行以下工作:

  • 将AOF重写缓冲区的类容写入到新的AOF缓冲期中,这时新AOF缓冲区中数据库的状态和服务器的当前状态一致
  • 对新的AOF文件改名,源自地(automic)覆盖现有的AOF文件,完成新旧AOF文件的替换。整个AOF重写期间,只有信号处理函数执行时会对服务器进程造成阻塞。  

3.1 redisServer结构体中与AOF相关的字段

struct redisServer{
    // AOF 状态(开启/关闭/可写)
    int aof_state;                  /* REDIS_AOF_(ON|OFF|WAIT_REWRITE) */

    // 所使用的 fsync 策略(每个写入/每秒/从不)
    int aof_fsync;                  /* Kind of fsync() policy */
    char *aof_filename;             /* Name of the AOF file */
    int aof_no_fsync_on_rewrite;    /* Don‘t fsync if a rewrite is in prog. */
    int aof_rewrite_perc;           /* Rewrite AOF if % growth is > M and... */
    off_t aof_rewrite_min_size;     /* the AOF file is at least N bytes. */

    // 最后一次执行 BGREWRITEAOF 时, AOF 文件的大小
    off_t aof_rewrite_base_size;    /* AOF size on latest startup or rewrite. */

    // AOF 文件的当前字节大小
    off_t aof_current_size;         /* AOF current size. */
    int aof_rewrite_scheduled;      /* Rewrite once BGSAVE terminates. */

    // 负责进行 AOF 重写的子进程 ID
    pid_t aof_child_pid;            /* PID if rewriting process */

    // AOF 重写缓存链表,链接着多个缓存块
    list *aof_rewrite_buf_blocks;   /* Hold changes during an AOF rewrite. */

    // AOF 缓冲区
    sds aof_buf;      /* AOF buffer, written before entering the event loop */

    // AOF 文件的描述符
    int aof_fd;       /* File descriptor of currently selected AOF file */

    // AOF 的当前目标数据库
    int aof_selected_db; /* Currently selected DB in AOF */

    // 推迟 write 操作的时间
    time_t aof_flush_postponed_start; /* UNIX time of postponed AOF flush */

    // 最后一直执行 fsync 的时间
    time_t aof_last_fsync;            /* UNIX time of last fsync() */
    time_t aof_rewrite_time_last;   /* Time used by last AOF rewrite run. */

    // AOF 重写的开始时间
    time_t aof_rewrite_time_start;  /* Current AOF rewrite start time. */

    // 最后一次执行 BGREWRITEAOF 的结果
    int aof_lastbgrewrite_status;   /* REDIS_OK or REDIS_ERR */

    // 记录 AOF 的 write 操作被推迟了多少次
    unsigned long aof_delayed_fsync;  /* delayed AOF fsync() counter */

    // 指示是否需要每写入一定量的数据,就主动执行一次 fsync()
    int aof_rewrite_incremental_fsync;/* fsync incrementally while rewriting? */
    int aof_last_write_status;      /* REDIS_OK or REDIS_ERR */
    int aof_last_write_errno;       /* Valid if aof_last_write_status is ERR */
    /* RDB persistence */

}        

3.2 重写AOF文件的命令

void bgrewriteaofCommand(client *c) {
    if (server.aof_child_pid != -1) {
    } else if (server.rdb_child_pid != -1) {
        server.aof_rewrite_scheduled = 1;
    } else if (rewriteAppendOnlyFileBackground() == C_OK) {
    } else {
    }  

3.3 serverCron定时程序,触发AOF重写

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    if (server.rdb_child_pid != -1 || server.aof_child_pid != -1 ||
        ldbPendingChildren())
    {
        ……
    } else {
        ……//检查是否触发AOF重写
         if (server.rdb_child_pid == -1 &&server.aof_child_pid == -1 &&
             server.aof_rewrite_perc &&server.aof_current_size > server.aof_rewrite_min_size)
         {
            long long base = server.aof_rewrite_base_size ?server.aof_rewrite_base_size : 1;
            long long growth = (server.aof_current_size*100/base) - 100;
            if (growth >= server.aof_rewrite_perc) {
                rewriteAppendOnlyFileBackground();
            }
         }
    }  

}  

后台重写的实现:

//后台重写AOF文件
int rewriteAppendOnlyFileBackground(void) {
    if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR;
    if (aofCreatePipes() != C_OK) return C_ERR;//创建父进程与子进程的管道
    openChildInfoPipe();
    start = ustime();
    if ((childpid = fork()) == 0) {
        char tmpfile[256];
        snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
        //在子进程中执行AOF重写
        if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
            ……
        }
    } else {
        /* Parent */ ……
    }
    return C_OK; /* unreached */
}
//重写AOF文件的程序
int rewriteAppendOnlyFile(char *filename) {
    snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) getpid());
    server.aof_child_diff = sdsempty();
    rioInitWithFile(&aof,fp);
    if (server.aof_rewrite_incremental_fsync)
        rioSetAutoSync(&aof,AOF_AUTOSYNC_BYTES);
    //遍历数据库,进行重写操作
    ……
    //写入、冲洗并同步到AOF文件
    if (rewriteAppendOnlyFileRio(&aof) == C_ERR) goto werr;
    if (fflush(fp) == EOF) goto werr;
    if (fsync(fileno(fp)) == -1) goto werr;
    ...
    return C_OK;
}//重写操作  

子进程重写完成后,父进程进行处理

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    if (server.rdb_child_pid != -1 || server.aof_child_pid != -1 ||
        ldbPendingChildren())
    {
        if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
            if(pid == server.aof_child_pid) {
            //子进程完成重写,父进程进行重写AOF文件的处理
                backgroundRewriteDoneHandler(exitcode,bysignal);
            }
        }
    }
}  
void backgroundRewriteDoneHandler(int exitcode, int bysignal) {    
    if (!bysignal && exitcode == 0) {
        snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof",
            (int)server.aof_child_pid);
        newfd = open(tmpfile,O_WRONLY|O_APPEND);
        if (aofRewriteBufferWrite(newfd) == -1) {
            ……//将重写缓冲区的数据写入到重写AOF文件
        }
        if (rename(tmpfile,server.aof_filename) == -1) {
            ……//覆盖旧的AOF文件
        }
        ……
    }
}  

 参考

redis aof持久化的源码分析

Redis持久化之AOF

时间: 2024-11-24 08:40:46

Redis持久化--AOF的相关文章

redis 持久化 AOF RDB

Redis的AOF持久化策略是将发送到redis服务端的每一条命令都记录下来,并且保存到硬盘中的AOF文件中,类似打日志文件,来一条命令就记录一条. AOF设置 AOF文件的位置和RDB文件的位置相同,都是通过dir参数设置,默认的文件名是appendonly.aof,可以通过appendfilename参数来修改. AOF测试 当客户端向服务器发送一些redis命令时,Redis会将所执行的命令记录到aof文件中,如下所示: 当redis服务器重启后,会将执行该aof文件,达到数据恢复的目的.

Redis持久化——AOF(二)

核心知识点: 1.AOF:以独立日志的方式记录写命令,重启时再执行命令.与RDB不同的是解决数据持久化的实时性,可以记录所有写操作. 2.AOF工作流程:写入命令.文件同步.文件重写.文件加载. 3.命令写入 a.将命令以文本协议格式保存在缓存中. b.为什么使用文本协议格式?兼容性.避免二次开销.可读性. c.为什么写入到缓存?这样不会受制于磁盘的IO性能 4.文件同步:从内存同步到文件中,有三种机制,使用的系统命令是write或fsync. (1).write会触发写延迟,依赖系统的调度机制

第五章:Redis持久化-AOF持久化

AOF持久化 AOF全称append only file持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的: AOF主要作用是解决了数据实时持久化的问题: 使用AOF 开始AOF需要设置appendonly yes,默认不开启. AOF文件名通过appendonlyname配置,默认文件名为appendonly.aof: AOF工作流程操作:命令写入(append).文件同步(sync).文件重写(rewrite).重启加载(reload): 所有写入命

由redis持久化AOF重写想到Excel保存过程

redis的持久机制有两种,一种是snapshot,另一种是AOF重写.第一种机制是按事先定制的策略,周期性地异步或同步将数据从内存同步至磁盘,在客户端的操作是由SAVE或BGSAVE命令执行.而AOF重写则是记录写操作至指定的文件尾部实现持久化.AOF重写过程如下: (1) redis主进程通过fork创建子进程 (2)子进程根据当前redis内存中的数据生成数据库重建命令序列到临时文件中 (3)父进程继承client的新请求,把请求中的写操作继续追加至原来的AOF文件(而不是直接写入临  

第十章 Redis持久化--RDB+AOF

注:本文主要参考自<Redis设计与实现> 1.Redis两种持久化方式 RDB 执行机制:快照,直接将databases中的key-value的二进制形式存储在了rdb文件中 优点:性能较高(因为是快照,且执行频率比aof低,而且rdb文件中直接存储的是key-values的二进制形式,对于恢复数据也快) 缺点:在save配置条件之间若发生宕机,此间的数据会丢失 AOF 执行机制:将对数据的每一条修改命令追加到aof文件 优点:数据不容易丢失 缺点:性能较低(每一条修改操作都要追加到aof文

redis持久化策略RDB和AOF

Redis 持久化: redis 提供了多种不同级别的持久化方式:一种是RDB,另一种是AOF. RDB 持久化可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot). AOF 持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集. AOF 文件中的命令全部以 Redis 协议的格式来保存,新命令会被追加到文件的末尾. Redis 还可以在后台对 AOF 文件进行重写(rewrite),使得 AOF 文件的体积不会超出保存

redis持久化RDB和AOF

Redis 持久化: 提供了多种不同级别的持久化方式:一种是RDB,另一种是AOF. RDB 持久化可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot). AOF 持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集. AOF 文件中的命令全部以 Redis 协议的格式来保存,新命令会被追加到文件的末尾. Redis 还可以在后台对 AOF 文件进行重写(rewrite),使得 AOF 文件的体积不会超出保存数据集状态所

Redis持久化之rdb&amp;aof

Redis有两种持久化的方式:快照(RDB文件)和追加式文件(AOF文件) RDB持久化方式是在一个特定的间隔保存某个时间点的一个数据快照. AOF(Append only file)持久化方式则会记录每一个服务器收到的写操作.数据回复时,这些记录的操作会逐条执行从而重建出原来的数据.写操作命令  记录的格式跟Redis协议一致,以追加的方式进行保存. Redis的持久化是可以禁用的,两种方式的持久化是可以同时存在的,但是当Redis重启时,AOF文件会被优先用于重建数据. 一.RDB RDB就

Redis的持久化-AOF

Redis的持久化AOF模式,以日志的形式记录服务器所处理的每一个写操作,在Redis服务启动之初会读取该文件来重新构建数据库,以保证启动后数据库中的数据是完整的. AOF的优点: 1.可以带来更高的数据安全性. 2.由于对日志文件的写入操作采用的是append模式,因此在写入过程汇总即使出现宕机,也不会破坏日志文件中已经存在的内容,然而如果我们本次操作写入一半数据就出现系统崩溃,可以在Redis下一次启动之前,通过redis-check-aof工具帮助解决数据一致性的问题. 3.如果日志过大,