成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Redis RDB 持久化源碼深度解析:從原理到實現

數據庫 Redis
為避免服務器宕機著情況導致redis內存數據庫數據丟失,redis默認出通過rdb保證可靠性,本文將從源碼的角度帶讀者了解rdb讀寫時機和寫入流程。

為避免服務器宕機著情況導致redis內存數據庫數據丟失,redis默認出通過rdb保證可靠性,本文將從源碼的角度帶讀者了解rdb讀寫時機和寫入流程。

save指令觸發rdb

redis支持通過命令的方式持久化內存數據庫數據,當我們鍵入save的時候,redis解析到這個指令之后,主線程直接調用saveCommand方法生成rdb文件落到磁盤中。

我們可以在rdb.c文件中看到該方法的實現,可以看到為了避免臟寫等問題,saveCommand會檢查當前是否有rdb子進程執行,如果沒有在子進程執行rdb持久化則直接調用rdbSave方法生成dump.rdb文件落盤:

//調用save指令其內部調用rdbSave完成rdb文件生成
void saveCommand(redisClient *c) {
 //檢查是否子進程執行rdb,若有則直接返回
    if (server.rdb_child_pid != -1) {
        addReplyError(c,"Background save already in progress");
        return;
    }
    //調用rdbSave
    if (rdbSave(server.rdb_filename) == REDIS_OK) {
        addReply(c,shared.ok);
    } else {
        addReply(c,shared.err);
    }
}

步入rdbSave即可看到生成臨時rdb寫入數據,然后數據刷盤,最后完成文件名原子修改的操作:

int rdbSave(char *filename) {
    char tmpfile[256];
    FILE *fp;
    rio rdb;
    int error;
 //生成一個tmp文件
    snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
    fp = fopen(tmpfile,"w");
    if (!fp) {
        redisLog(REDIS_WARNING, "Failed opening .rdb for saving: %s",
            strerror(errno));
        return REDIS_ERR;
    }
 //調用rdbSaveRio完成數據寫入
    rioInitWithFile(&rdb,fp);
    if (rdbSaveRio(&rdb,&error) == REDIS_ERR) {
        errno = error;
        goto werr;
    }
 //直接刷盤到磁盤,避免留在系統輸出緩沖區
    /* Make sure data will not remain on the OS's output buffers */
    if (fflush(fp) == EOF) goto werr;
    if (fsync(fileno(fp)) == -1) goto werr;
    if (fclose(fp) == EOF) goto werr;

    //完成寫入后文件重命名為dump.rdb
    if (rename(tmpfile,filename) == -1) {
        redisLog(REDIS_WARNING,"Error moving temp DB file on the final destination: %s", strerror(errno));
        unlink(tmpfile);
        return REDIS_ERR;
    }
    //......
    
    return REDIS_OK;

 //......
}

bgsave指令觸發rdb

同時redis也支持后臺持久化,如果用戶需要考慮redis性能問題,可以直接通過bgsave指令創建rdb子進程完成數據庫數據持久化。

我們同樣可以在rdb.c文件中看到bgsave指令調用的方法bgsaveCommand,可以看到如果沒有子進程進行rdb或者aof,該指令會調用rdbSaveBackground完成異步數據持久化:

//調用rdbSaveBackground創建一個子進程生成rdb文件,不影響主線程
void bgsaveCommand(redisClient *c) {
 //如果有子進程執行rdb或者aof,則直接返回錯誤提醒
    if (server.rdb_child_pid != -1) {
        addReplyError(c,"Background save already in progress");
    } else if (server.aof_child_pid != -1) {
        addReplyError(c,"Can't BGSAVE while AOF log rewriting is in progress");
    } else if (rdbSaveBackground(server.rdb_filename) == REDIS_OK) {//調用rdbSaveBackground進行數據持久化
        addReplyStatus(c,"Background saving started");
    } else {
        addReply(c,shared.err);
    }
}

步入rdbSaveBackground可以看到,其內部還會檢查一次是否有文件進行rdb,如果明確沒有之后直接fork一個子進程出來調用上文所說的rdbSave完成數據持久化到dump.rdb中:

int rdbSaveBackground(char *filename) {
    pid_t childpid;
    long long start;

    if (server.rdb_child_pid != -1) return REDIS_ERR;

     //......

    start = ustime();
    if ((childpid = fork()) == 0) {//創建子進程
        int retval;

        //......
        retval = rdbSave(filename);//生成rdb文件
       
        exitFromChild((retval == REDIS_OK) ? 0 : 1);//退出子進程
    } else {
       //......
    }
    return REDIS_OK; /* unreached */
}

rdb被動觸發

redis被動觸發由時間事件輪詢處理,我們可以在redis.conf配置rdb被動觸發持久化的時機,默認配置如下當60s生成10000或者300s 生成10次改變亦或者900s生成1次改變,我們就會執行一次被動rdb持久化:

save 900 1
save 300 10
save 60 10000

對應的我們可以在redis.c的serverCron函數在看到這段邏輯,它會遍歷出我們配置的保存間隔配置saveparam,通過比對這3條配置的上次保存時間計算出時間間隔,以及當前redis變化書dirty看看是否符合要求,若如何要求則進行后臺rdb持久化:

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
   //......

    /* Check if a background saving or AOF rewrite in progress terminated. */
    if (server.rdb_child_pid != -1 || server.aof_child_pid != -1) {
        //......
        }
    } else {
        //遍歷3個配置的params,如果改變數和事件間隔配置要求則直接進行后臺被動rdb持久化
         for (j = 0; j < server.saveparamslen; j++) {
            struct saveparam *sp = server.saveparams+j;

            
            if (server.dirty >= sp->changes && //查看變化數是否大于當前配置的changes
                server.unixtime-server.lastsave > sp->seconds && //查看時間間隔是否大于配置
                (server.unixtime-server.lastbgsave_try >
                 REDIS_BGSAVE_RETRY_DELAY ||
                 server.lastbgsave_status == REDIS_OK))
            {
              //......
              //執行異步持久化
                rdbSaveBackground(server.rdb_filename);
                break;
            }
         }

         //......
         }
    }


  //......
 
    return 1000/server.hz;
}

其他被動落盤時機

其實有些時候我們執行的某些執行也會進行rdb持久化,例如flushall刷盤指令,其調用函數flushallCommand就會時間串行執行rdb持久化:

//調用flush指令時會調用rdbSave進行數據持久化
void flushallCommand(redisClient *c) {
   //......
    if (server.saveparamslen > 0) {
        //串行執行rdb持久化
        int saved_dirty = server.dirty;
        rdbSave(server.rdb_filename);
       //......
    }
    server.dirty++;
}

當我們關閉redis服務器的時候也會執行rdb串行持久化:

//服務器進程關閉時調用rdbSave生成rdb文件
int prepareForShutdown(int flags) {
      //......
    if (server.rdb_child_pid != -1) {
        //......
    }
    if (server.aof_state != REDIS_AOF_OFF) {
       //......
    }
    if ((server.saveparamslen > 0 && !nosave) || save) {
      
        if (rdbSave(server.rdb_filename) != REDIS_OK) {
             //......
            return REDIS_ERR;
        }
    }
      //......
    return REDIS_OK;
}

rdb寫入文件數據詳解

無論是rdbsave還是rdbbgsave對應的方法,其內部都會調用rdbSaveRio,它進行文件寫入時對應寫入數據大體順序是:

  • 寫入REDIS大寫。
  • 補0填充長度。
  • 寫入當前redis版本號,以筆者源碼為例則是6。
  • 遍歷數據庫寫入REDIS_RDB_OPCODE_SELECTDB表示開始存儲數據庫數據,這個值默認為254,redis會轉為八進制376寫入。
  • 遍歷當前數據庫鍵值對key長度和key,value長度和value寫入,后續數據庫都是如此往復。
  • 所有數據庫寫完后補上REDIS_RDB_OPCODE_EOF和checksum用于后續rdb數據恢復的校驗。

為保證讀者更直觀的了解redis持久化寫入的內容,我們可以刪除本地rdb文件,然后執行如下執行生成一個全新的rdb文件:

# 保存鍵值對
set key value
# 切換到1庫
select 1
# 保存鍵值對到1庫
set key-1 value
# 調用save進行數據持久化
save

正常情況下我們打開rdb文件會得到一堆類型亂碼的內容,我們無法知曉寫入的信息,我們可以直接鍵入od生成rdb文件16進制數據及其對應的ASCII字符:

od -A x -t x1c -v dump.rdb

最終我們就可以得到如下文件,可以看到數據格式和筆者上文所說基本一致:

#        大寫REDIS          補0            254的8進制 當前數據庫索引   鍵值對`key`長度和`key`,`value`長度和`value`      
#000000  52  45  44  49  53  30  30  30  36  fe  00  00  03  6b  65  79
         R   E   D   I   S   0   0   0   6 376  \0  \0 003   k   e   y
000010  05  76  61  6c  75  65  fe  01  00  05  6b  65  79  2d  31  05
       005   v   a   l   u   e 
#  254的8進制 當前數據庫索引1  鍵值對key長度和key,value長度和value    
376 001  \0 005   k   e   y   -   1 005
000020  76  61  6c  75  65  ff  76  eb  e4  80  bd  df  66  11
         v   a   l   u   e 
# EOF 255八進制 剩下8位是對應的checksum
377   v 353 344 200 275 337   f 021
00002e

對應的我們給出這段源碼,對應的寫入流程如上文筆者所述:

int rdbSaveRio(rio *rdb, int *error) {
    dictIterator *di = NULL;
    dictEntry *de;
    char magic[10];
    int j;
    long long now = mstime();
    uint64_t cksum;

    if (server.rdb_checksum)
        rdb->update_cksum = rioGenericUpdateChecksum;
    snprintf(magic,sizeof(magic),"REDIS%04d",REDIS_RDB_VERSION);//對應redis 3個0 然后版本號,當前版本為6
    if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;//上述魔數寫入rdb文件
  //遍歷數據庫
    for (j = 0; j < server.dbnum; j++) {
        redisDb *db = server.db+j;
        dict *d = db->dict;
        if (dictSize(d) == 0) continue;
        di = dictGetSafeIterator(d);
        if (!di) return REDIS_ERR;

        /* Write the SELECT DB opcode */
        if (rdbSaveType(rdb,REDIS_RDB_OPCODE_SELECTDB) == -1) goto werr;//寫入254,也就是內容中的376
        if (rdbSaveLen(rdb,j) == -1) goto werr;//寫入當前庫索引

        //遍歷當前鍵值對寫入
        while((de = dictNext(di)) != NULL) {
            sds keystr = dictGetKey(de);
            robj key, *o = dictGetVal(de);
            long long expire;

            initStaticStringObject(key,keystr);
            expire = getExpire(db,&key);
            if (rdbSaveKeyValuePair(rdb,&key,o,expire,now) == -1) goto werr;//寫入鍵值對
        }
        dictReleaseIterator(di);
    }
  //......

    /* EOF opcode */
    if (rdbSaveType(rdb,REDIS_RDB_OPCODE_EOF) == -1) goto werr;//寫入結束符254 八進制為377

 
    cksum = rdb->cksum;
    memrev64ifbe(&cksum);
    if (rioWrite(rdb,&cksum,8) == 0) goto werr;//寫入8位數校驗和,其底層調用rioGenericUpdateChecksum,按照cksum到數組中獲取就對應的值并
    return REDIS_OK;

//......
}

對應的我們步入rdbSaveKeyValuePair即可看到redis獲取key長度和key,以及value長度和value并寫入rdb文件的核心流程:

int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val,
                        long long expiretime, long long now)
{
    //......

    /* Save type, key, value */
    if (rdbSaveObjectType(rdb,val) == -1) return -1;//寫入類型以字符串形式就是0
    if (rdbSaveStringObject(rdb,key) == -1) return -1;//寫入key長度和key
    if (rdbSaveObject(rdb,val) == -1) return -1;//寫入value長度和value
    return 1;
}

責任編輯:趙寧寧 來源: 寫代碼的SharkChili
相關推薦

2024-07-07 21:49:22

2023-10-12 13:01:29

Redis數據庫

2024-03-26 00:03:08

Redis數據RDB

2019-11-18 16:20:48

RedisRDB數據庫

2024-12-20 12:15:06

RedisRDB持久化

2023-05-11 09:12:35

RedisRDB日志

2021-07-18 07:59:42

RedisRDBAOF

2025-04-02 07:29:14

2024-09-12 08:49:53

2019-05-17 08:55:49

RedisRDBAOF

2025-04-03 00:03:00

數據內存網絡

2020-02-18 16:14:33

RedisRDBAOF

2021-10-04 21:11:18

Redis混合持久化

2024-09-06 17:49:46

2021-03-10 00:02:01

Redis

2021-10-18 07:43:30

RedisAOF日志RDB快照

2023-03-13 08:08:48

數據庫Redis

2022-02-28 10:05:12

組件化架構設計從原組件化模塊化

2021-05-11 07:51:30

React ref 前端

2011-06-07 17:16:47

iPhone 數據
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 午夜精品一区二区三区在线播放 | 久久久精品综合 | 中文字幕一区二区在线观看 | 日韩精品 电影一区 亚洲 | 欧美日韩一区在线观看 | 四虎永久免费黄色影片 | 久久亚洲综合 | 久久久精品影院 | 色综合美女 | 国产一区二区a | 欧美日韩在线精品 | 四虎精品在线 | 日韩精品a在线观看图片 | 国产精品一区二区视频 | 九九在线视频 | 亚洲 精品 综合 精品 自拍 | 国产欧美精品一区二区三区 | 97精品一区二区 | 国产乱码精品一区二区三区av | 国产精品成人一区二区 | 国产视频久久久 | 热久久999 | 久久国产精品色av免费观看 | 在线欧美视频 | 天天碰日日操 | 亚州成人| 国产精品视频区 | 日韩毛片视频 | 精品美女在线观看视频在线观看 | 亚洲国产自产 | 亚洲乱码一区二区三区在线观看 | 91精品久久久久久久久 | 国产综合精品一区二区三区 | 亚洲人成人网 | 亚洲日本一区二区 | 国产三区视频在线观看 | 色欧美综合 | 亚洲成人在线免费 | 日韩欧美国产一区二区 | 久久久久国产一区二区三区四区 | 久久久久精 |