国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

Redis RDB 持久化源碼深度解析:從原理到實(shí)現(xiàn)

數(shù)據(jù)庫 Redis
為避免服務(wù)器宕機(jī)著情況導(dǎo)致redis內(nèi)存數(shù)據(jù)庫數(shù)據(jù)丟失,redis默認(rèn)出通過rdb保證可靠性,本文將從源碼的角度帶讀者了解rdb讀寫時(shí)機(jī)和寫入流程。

為避免服務(wù)器宕機(jī)著情況導(dǎo)致redis內(nèi)存數(shù)據(jù)庫數(shù)據(jù)丟失,redis默認(rèn)出通過rdb保證可靠性,本文將從源碼的角度帶讀者了解rdb讀寫時(shí)機(jī)和寫入流程。

save指令觸發(fā)rdb

redis支持通過命令的方式持久化內(nèi)存數(shù)據(jù)庫數(shù)據(jù),當(dāng)我們鍵入save的時(shí)候,redis解析到這個(gè)指令之后,主線程直接調(diào)用saveCommand方法生成rdb文件落到磁盤中。

我們可以在rdb.c文件中看到該方法的實(shí)現(xiàn),可以看到為了避免臟寫等問題,saveCommand會(huì)檢查當(dāng)前是否有rdb子進(jìn)程執(zhí)行,如果沒有在子進(jìn)程執(zhí)行rdb持久化則直接調(diào)用rdbSave方法生成dump.rdb文件落盤:

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

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

int rdbSave(char *filename) {
    char tmpfile[256];
    FILE *fp;
    rio rdb;
    int error;
 //生成一個(gè)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;
    }
 //調(diào)用rdbSaveRio完成數(shù)據(jù)寫入
    rioInitWithFile(&rdb,fp);
    if (rdbSaveRio(&rdb,&error) == REDIS_ERR) {
        errno = error;
        goto werr;
    }
 //直接刷盤到磁盤,避免留在系統(tǒng)輸出緩沖區(qū)
    /* 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指令觸發(fā)rdb

同時(shí)redis也支持后臺(tái)持久化,如果用戶需要考慮redis性能問題,可以直接通過bgsave指令創(chuàng)建rdb子進(jìn)程完成數(shù)據(jù)庫數(shù)據(jù)持久化。

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

//調(diào)用rdbSaveBackground創(chuàng)建一個(gè)子進(jìn)程生成rdb文件,不影響主線程
void bgsaveCommand(redisClient *c) {
 //如果有子進(jìn)程執(zhí)行rdb或者aof,則直接返回錯(cuò)誤提醒
    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) {//調(diào)用rdbSaveBackground進(jìn)行數(shù)據(jù)持久化
        addReplyStatus(c,"Background saving started");
    } else {
        addReply(c,shared.err);
    }
}

步入rdbSaveBackground可以看到,其內(nèi)部還會(huì)檢查一次是否有文件進(jìn)行rdb,如果明確沒有之后直接fork一個(gè)子進(jìn)程出來調(diào)用上文所說的rdbSave完成數(shù)據(jù)持久化到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) {//創(chuàng)建子進(jìn)程
        int retval;

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

rdb被動(dòng)觸發(fā)

redis被動(dòng)觸發(fā)由時(shí)間事件輪詢處理,我們可以在redis.conf配置rdb被動(dòng)觸發(fā)持久化的時(shí)機(jī),默認(rèn)配置如下當(dāng)60s生成10000或者300s 生成10次改變亦或者900s生成1次改變,我們就會(huì)執(zhí)行一次被動(dòng)rdb持久化:

save 900 1
save 300 10
save 60 10000

對(duì)應(yīng)的我們可以在redis.c的serverCron函數(shù)在看到這段邏輯,它會(huì)遍歷出我們配置的保存間隔配置saveparam,通過比對(duì)這3條配置的上次保存時(shí)間計(jì)算出時(shí)間間隔,以及當(dāng)前redis變化書dirty看看是否符合要求,若如何要求則進(jìn)行后臺(tái)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個(gè)配置的params,如果改變數(shù)和事件間隔配置要求則直接進(jìn)行后臺(tái)被動(dòng)rdb持久化
         for (j = 0; j < server.saveparamslen; j++) {
            struct saveparam *sp = server.saveparams+j;

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

         //......
         }
    }


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

其他被動(dòng)落盤時(shí)機(jī)

其實(shí)有些時(shí)候我們執(zhí)行的某些執(zhí)行也會(huì)進(jìn)行rdb持久化,例如flushall刷盤指令,其調(diào)用函數(shù)flushallCommand就會(huì)時(shí)間串行執(zhí)行rdb持久化:

//調(diào)用flush指令時(shí)會(huì)調(diào)用rdbSave進(jìn)行數(shù)據(jù)持久化
void flushallCommand(redisClient *c) {
   //......
    if (server.saveparamslen > 0) {
        //串行執(zhí)行rdb持久化
        int saved_dirty = server.dirty;
        rdbSave(server.rdb_filename);
       //......
    }
    server.dirty++;
}

當(dāng)我們關(guān)閉redis服務(wù)器的時(shí)候也會(huì)執(zhí)行rdb串行持久化:

//服務(wù)器進(jìn)程關(guān)閉時(shí)調(diào)用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寫入文件數(shù)據(jù)詳解

無論是rdbsave還是rdbbgsave對(duì)應(yīng)的方法,其內(nèi)部都會(huì)調(diào)用rdbSaveRio,它進(jìn)行文件寫入時(shí)對(duì)應(yīng)寫入數(shù)據(jù)大體順序是:

  • 寫入REDIS大寫。
  • 補(bǔ)0填充長(zhǎng)度。
  • 寫入當(dāng)前redis版本號(hào),以筆者源碼為例則是6。
  • 遍歷數(shù)據(jù)庫寫入REDIS_RDB_OPCODE_SELECTDB表示開始存儲(chǔ)數(shù)據(jù)庫數(shù)據(jù),這個(gè)值默認(rèn)為254,redis會(huì)轉(zhuǎn)為八進(jìn)制376寫入。
  • 遍歷當(dāng)前數(shù)據(jù)庫鍵值對(duì)key長(zhǎng)度和key,value長(zhǎng)度和value寫入,后續(xù)數(shù)據(jù)庫都是如此往復(fù)。
  • 所有數(shù)據(jù)庫寫完后補(bǔ)上REDIS_RDB_OPCODE_EOF和checksum用于后續(xù)rdb數(shù)據(jù)恢復(fù)的校驗(yàn)。

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

# 保存鍵值對(duì)
set key value
# 切換到1庫
select 1
# 保存鍵值對(duì)到1庫
set key-1 value
# 調(diào)用save進(jìn)行數(shù)據(jù)持久化
save

正常情況下我們打開rdb文件會(huì)得到一堆類型亂碼的內(nèi)容,我們無法知曉寫入的信息,我們可以直接鍵入od生成rdb文件16進(jìn)制數(shù)據(jù)及其對(duì)應(yīng)的ASCII字符:

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

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

#        大寫REDIS          補(bǔ)0            254的8進(jìn)制 當(dāng)前數(shù)據(jù)庫索引   鍵值對(duì)`key`長(zhǎng)度和`key`,`value`長(zhǎng)度和`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進(jìn)制 當(dāng)前數(shù)據(jù)庫索引1  鍵值對(duì)key長(zhǎng)度和key,value長(zhǎng)度和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八進(jìn)制 剩下8位是對(duì)應(yīng)的checksum
377   v 353 344 200 275 337   f 021
00002e

對(duì)應(yīng)的我們給出這段源碼,對(duì)應(yīng)的寫入流程如上文筆者所述:

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);//對(duì)應(yīng)redis 3個(gè)0 然后版本號(hào),當(dāng)前版本為6
    if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;//上述魔數(shù)寫入rdb文件
  //遍歷數(shù)據(jù)庫
    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,也就是內(nèi)容中的376
        if (rdbSaveLen(rdb,j) == -1) goto werr;//寫入當(dāng)前庫索引

        //遍歷當(dāng)前鍵值對(duì)寫入
        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;//寫入鍵值對(duì)
        }
        dictReleaseIterator(di);
    }
  //......

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

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

//......
}

對(duì)應(yīng)的我們步入rdbSaveKeyValuePair即可看到redis獲取key長(zhǎng)度和key,以及value長(zhǎng)度和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長(zhǎng)度和key
    if (rdbSaveObject(rdb,val) == -1) return -1;//寫入value長(zhǎng)度和value
    return 1;
}

責(zé)任編輯:趙寧寧 來源: 寫代碼的SharkChili
相關(guān)推薦

2023-10-12 13:01:29

Redis數(shù)據(jù)庫

2024-03-26 00:03:08

Redis數(shù)據(jù)RDB

2024-07-07 21:49:22

2019-11-18 16:20:48

RedisRDB數(shù)據(jù)庫

2023-05-11 09:12:35

RedisRDB日志

2024-12-20 12:15:06

RedisRDB持久化

2021-07-18 07:59:42

RedisRDBAOF

2025-04-02 07:29:14

2019-05-17 08:55:49

RedisRDBAOF

2024-09-12 08:49:53

2021-10-04 21:11:18

Redis混合持久化

2020-02-18 16:14:33

RedisRDBAOF

2021-10-18 07:43:30

RedisAOF日志RDB快照

2021-03-10 00:02:01

Redis

2025-04-03 00:03:00

數(shù)據(jù)內(nèi)存網(wǎng)絡(luò)

2024-09-06 17:49:46

2023-03-13 08:08:48

數(shù)據(jù)庫Redis

2025-07-23 08:19:52

網(wǎng)絡(luò)通信DHCP網(wǎng)絡(luò)

2025-12-02 01:45:00

2021-05-26 11:30:24

Java線程池代碼
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

奶水喷射视频一区| 国产精品成人v| 97视频在线播放| 欧美美女搞黄| 欧美成人蜜桃| jlzzjlzz亚洲女人| 日本不卡高字幕在线2019| 亚洲欧美电影一区二区| 精品一区二区三区中文字幕| 爱啪啪综合导航| 欧美精品1区2区3区| 久久免费看视频| 99久久99久久精品免费观看| 日本一区免费观看| 久久国产精品毛片| 日韩久久不卡| 国产精品一区二区不卡| 免费无码毛片一区二三区| 日本欧美在线观看| 久久影视免费观看| 国产精品日韩精品在线播放 | 91视频国产资源| 欧美亚洲日本在线观看| 亚洲国产精品精华液ab| 国产剧情演绎av| 欧美日韩亚洲一区二| 国产情侣av自拍| 精品动漫一区二区三区| 日本成人三级电影| 国产精品久久精品| 国产成人小视频| 青梅竹马是消防员在线| 久久精品国产亚洲一区二区| 久久社区一区| 成人在线免费播放视频| 日韩欧美国产小视频| 狠狠久久伊人| 国产精品一区二区欧美| 国产精品看片你懂得| 国产乱码午夜在线视频| 亚洲free性xxxx护士hd| 国产精品网曝门| 欧美xxxxxx| 午夜精品一区二区三区四区| 国产精品天天看| 久久久99精品视频| 国产毛片精品视频| 伊人国产在线| 亚洲国产精品va| 欧美aaa大片视频一二区| 日本aⅴ大伊香蕉精品视频| 99精品免费视频| 日韩久久精品一区二区三区| 国产一区二区三区久久久久久久久 | 国产成人精品视频ⅴa片软件竹菊| 亚洲激情男女视频| 岛国在线大片| 91精品国产色综合久久不卡98口 | 日韩一区二区三区免费播放| 国产不卡一区二区三区在线观看| 国产一区二区h| 一区二区三区区四区播放视频在线观看| 欧美日韩成人综合在线一区二区 | 亚洲h色精品| 黄网站色视频免费观看| 色哟哟国产精品| 日本午夜免费一区二区| 91在线观看免费高清| 国产精品国产自产拍高清av王其| 精品视频一二区| 亚洲男人天堂古典| 欧美精品啪啪| 少妇无码av无码专区在线观看 | 国产一区二区三区免费在线| 69堂成人精品视频免费| 亚洲欧美综合在线精品| 电影91久久久| 日本在线观看一区二区| 日本麻豆一区二区三区视频| 国产91在线播放精品91| 无码小电影在线观看网站免费| 成人蜜臀av电影| 在线h片观看| 国产伦精品免费视频| 亚洲视频一区在线| 少妇精品视频一区二区免费看| 国产精品xxxx| 日韩欧美国产激情| 国产成人手机高清在线观看网站| 免费看成人午夜电影| 欧美精品久久99| 久久精品一区二区国产| 黄色av免费在线观看| 国产啪精品视频| 亚洲精品乱码久久久久| 精品久久久久久久久久久aⅴ| gay视频丨vk| 国产成人精品日本亚洲专区61| 久久亚洲免费视频| 亚洲肉体裸体xxxx137| 国产超碰在线观看| 国产精华一区二区三区| 日本福利一区二区| 日韩电影一二三区| 在线黄色网页| 久青草国产97香蕉在线视频| 国产白丝精品91爽爽久久| 欧美日韩卡一| 在线观看入口黄最新永久免费国产| 免费国产成人看片在线| 久久成人人人人精品欧| 欧美三级小说| 久久在线中文字幕| 欧洲一区二区三区在线| 9999精品视频| 国产精品香蕉视屏| 国内精品久久久久影院薰衣草| 毛片免费不卡| 国产99久久久欧美黑人 | 性刺激综合网| 国产人成亚洲第一网站在线播放| 亚洲视频第二页| 色呦呦日韩精品| 日本激情视频在线观看| 裸体大乳女做爰69| 亚洲一区二区在线播放| 国产精品日韩久久久久| 亚洲另类在线视频| 李宗瑞系列合集久久| 久久这里有精品| 精品欧美乱码久久久久久| 久久er精品视频| 国产黄在线看| 国产欧美一区二区| 欧美日韩在线一二三| 95精品视频| 亚洲精品成人久久久998| 精品国产一区久久| 一区二区三区在线播| av亚洲精华国产精华精华| 国产精品久久久久无码av| 蜜桃精品噜噜噜成人av| 国产直播在线| 欧美色18zzzzxxxxx| 日韩一级片免费视频| 四虎影视永久免费在线观看一区二区三区| 国产精品高潮呻吟视频| 992tv成人免费视频| 久久久久久国产精品三级玉女聊斋| 91久久精品国产91性色tv| 97久久人人超碰| 不卡在线视频中文字幕| 99热这里都是精品| av在线一区二区| 午夜在线一区二区| 国产欧美日本| 日韩av电影天堂| 国产精品一区二区x88av| 福利电影一区二区三区| 久久99精品国产麻豆婷婷| 午夜羞羞小视频在线观看| 9l视频白拍9色9l视频| 黄色网页免费在线观看| 1024欧美极品| 在线小视频网址| 免费在线观看av网站| 97电影在线看视频| 美女胸又www又黄的网站| 日本午夜视频| 特级毛片在线观看| 男人添女人下部视频免费| 在线一区高清| 一本色道久久99精品综合| 国产夫妻自拍一区| 久久久久狠狠高潮亚洲精品| 小草在线视频在线免费视频| 久久国产情侣| 中文字幕免费在线| 国产一区二区主播在线| 美女扒开腿让男人桶爽久久软| 草莓视频成人appios| 免费污视频在线一区| 99久热在线精品视频观看| 日韩大尺度黄色| 97视频热人人精品免费| 国产日韩精品一区二区三区| 成人网在线播放| 黑人精品欧美一区二区蜜桃| 欧洲乱码伦视频免费| 亚洲91在线| 国产无套精品一区二区| 57pao成人永久免费视频| 午夜精品久久久久久久久| 97se亚洲国产综合自在线不卡 | 快播亚洲色图| 久热精品视频在线观看| 国产欧美日韩在线观看| 亚洲va欧美va国产va天堂影院| 5566中文字幕一区二区电影 | 午夜爽爽爽男女免费观看影院|