美文网首页
Redis深度历险-AOF持久化

Redis深度历险-AOF持久化

作者: 突击手平头哥 | 来源:发表于2021-10-31 19:41 被阅读0次

    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持久化

          本文链接:https://www.haomeiwen.com/subject/bwtaaltx.html