Redis深度历险-AOF持久化

Redis深度历险-AOF持久化

Redis提供两种持久化方式AOF和RDB,RDB是快照形式持久化全量数据、AOF是增量持久化记录执行命令

AOF原理

struct redisServer {
  ........
    sds aof_buf;            //AOF缓冲区

  AOF持久化的是Redis执行的写入命令,Redis会将执行的写入命令放入AOF缓冲区中;而在Redis的定时任务server.c/serverCron中将数据写入到磁盘中

  在恢复数据时按照顺序依次执行即可恢复数据。

AOF配置

  • appendonly yes:是否开启AOF持久化
  • appendfilename appendonly.aof:文件名
  • appendfsync:磁盘写入策略,主要是因为文件IO有缓冲区可能数据没有真正写入到文件
    • always:每次写入磁盘,性能较差
    • everysec:每秒写入一次
    • no:系统处理缓存回写

  在这里面影响最大的是磁盘写入策略,因为调用write写入的是系统缓冲区而没有真正落盘,如果系统发生宕机则会造成数据丢失,不过Linux提供fsyncfdatasync等接口将数据刷入硬盘中

  磁盘写入策略就是控制落盘的时机,需要根据系统需求在数据和性能中作出取舍

  当然,调用write接口也是注册事件到Redis的事件循环中一并处理的

AOF实现

void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
    ........
    
    //将命令写入到aof缓冲区中
    if (server.aof_state == AOF_ON)
        server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf));

    //如果正在进程AOF重写时,则同样将输入发送给AOF重写子进程
    if (server.child_type == CHILD_TYPE_AOF)
        aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));

AOF重写

AOF的问题在于存储的是明文命令且存在冗余,文件内容会非常大,在恢复数据时耗时非常长

AOF重写原理

SET "A" "B"
DEL "A"
SET "A" "C"
SET "A" "D"

类似于这样执行后,Redis中实际上只有A这样一个键,AOF中存储的四条命令实际上存在冗余;AOF重写的原理就是根据内存中的数据将AOF文件重写为:

SET "A" "D"

在恢复数据时最终得出的结果时一致的

AOF重写实现

执行bgrewriteaof命令即可进行AOF重写

int rewriteAppendOnlyFileBackground(void) {
    pid_t childpid;

    if (hasActiveChildProcess()) return C_ERR;
    if (aofCreatePipes() != C_OK) return C_ERR;
    //创建一个子进程来执行重写指令
    if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) {
        char tmpfile[256];
                
        //并不是在AOF文件中直接重写,而是重写到一个新的文件最终原子行替换
        redisSetProcTitle("redis-aof-rewrite");
        redisSetCpuAffinity(server.aof_rewrite_cpulist);
        snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
        if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
            sendChildCowInfo(CHILD_INFO_TYPE_AOF_COW_SIZE, "AOF rewrite");
            exitFromChild(0);
        } else {
            exitFromChild(1);
        }
    } else {
        /* Parent */
        if (childpid == -1) {
            serverLog(LL_WARNING,
                "Can't rewrite append only file in background: fork: %s",
                strerror(errno));
            aofClosePipes();
            return C_ERR;
        }
        serverLog(LL_NOTICE,
            "Background append only file rewriting started by pid %ld",(long) childpid);
        server.aof_rewrite_scheduled = 0;
        server.aof_rewrite_time_start = time(NULL);

        server.aof_selected_db = -1;
        replicationScriptCacheFlush();
        return C_OK;
    }
    return C_OK; /* unreached */
}
  • AOF重写是不会阻塞Redis执行的,因为创建了子进程来执行
  • AOF重写也是不会影响AOF文件的,是在一个临时文件中写入数据的,最终用rename来替换
int rewriteAppendOnlyFileRio(rio *aof) {
    dictIterator *di = NULL;
    dictEntry *de;
    size_t processed = 0;
    int j;
    long key_count = 0;
    long long updated_time = 0;
        
    //逐个遍历所有db
    for (j = 0; j < server.dbnum; j++) {
        char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n";
        redisDb *db = server.db+j;
        dict *d = db->dict;
        if (dictSize(d) == 0) continue;
        di = dictGetSafeIterator(d);

        //在db切换时记录select命令
        if (rioWrite(aof,selectcmd,sizeof(selectcmd)-1) == 0) goto werr;
        if (rioWriteBulkLongLong(aof,j) == 0) goto werr;

        //直接遍历内存中的字典
        while((de = dictNext(di)) != NULL) {
            sds keystr;
            robj key, *o;
            long long expiretime;

            keystr = dictGetKey(de);
            o = dictGetVal(de);
            initStaticStringObject(key,keystr);

            expiretime = getExpire(db,&key);

            //存储命令、key、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;
    ........

AOF重写直接就是遍历所有数据库的所有键值,存储到AOF文件中

AOF重写缓冲区

AOF重写过程中主进程接收到新的指令就会放入到AOF重写缓冲区中

子进程
int rewriteAppendOnlyFile(char *filename) {
  ........
      while(mstime()-start < 1000 && nodata < 20) {
        if (aeWait(server.aof_pipe_read_data_from_parent, AE_READABLE, 1) <= 0)
        {
            nodata++;
            continue;
        }
        nodata = 0; /* Start counting from zero, we stop on N *contiguous*
                       timeouts. */
        aofReadDiffFromParent();
    }

同时AOF重写子进程会接收到重写过程中Redis进程执行的指令并同样记录到文件中

父进程
void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {
    listNode *ln = listLast(server.aof_rewrite_buf_blocks);
    ........

重写缓冲区是以链表实现的,一段一段的通过事件循环发送给子进程;最终如果子进程结束后还有剩余,由主进程将数据追加写入到重写后的AOF文件中

你可能感兴趣的:(Redis深度历险-AOF持久化)