日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務,提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

redis支持2種持久化功能,分別是RDB持久化和AOF(Append Only File)持久化。

今天總結下redis加載AOF文件過程,以及加載AOF過程中遇到的問題。

由于AOF文件里面包含了重建redis數(shù)據(jù)庫狀態(tài)所需要的所有命令,因此在redis啟動過程中需要加載一次AOF文件(前提是redis配置文件中使用的是aof文件),這樣就可以還原之前的redis所有狀態(tài)了。

redis在啟動時加載AOF過程如下

int main(int argc, char **argv) {
  ...
   // 如果服務器不是運行在 SENTINEL 模式,那么執(zhí)行以下代碼
    if (!server.sentinel_mode) { //sentinel和集群只能二選1
        /* Things not needed when running in Sentinel mode. */
        redisLog(REDIS_WARNING,"Server started, Redis version " REDIS_VERSION);
    #ifdef __linux__
        linuxOvercommitMemoryWarning();
    #endif
        // 從 AOF 文件或者 RDB 文件中載入數(shù)據(jù)
        loadDataFromDisk();
        // 判斷是否啟動集群
        if (server.cluster_enabled) { //在該函數(shù)前會先載入cluster配置nodes.conf,見initServer->clusterInit;
            if (verifyClusterConfigWithData() == REDIS_ERR) {
                redisLog(REDIS_WARNING,
                    "You can't have keys in a DB different than DB 0 when in "
                    "Cluster mode. Exiting.");
                exit(1);
            }
        }
        // 打印 TCP 端口
        if (server.ipfd_count > 0)
            redisLog(REDIS_NOTICE,"The server is now ready to accept connections on port %d", server.port);
        // 打印本地套接字端口
        if (server.sofd > 0)
            redisLog(REDIS_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
    } else { //sentinel和集群只能二選1
        sentinelIsRunning();
    }


    /* Warning the user about suspicious maxmemory setting. */
    // 檢查不正常的 maxmemory 配置
    if (server.maxmemory > 0 && server.maxmemory < 1024*1024) {
        redisLog(REDIS_WARNING,"WARNING: You specified a maxmemory value that is less than 1MB (current value is %llu bytes). Are you sure this is what you really want?", server.maxmemory);
    }


    // 運行事件處理器,一直到服務器關閉為止
    aeSetBeforeSleepProc(server.el,beforeSleep);
    aeMain(server.el); 


    // 服務器關閉,停止事件循環(huán)
    aeDeleteEventLoop(server.el);


    return 0;
}

從上面代碼得知,redis在loadDataFromDisk()中加載AOF文件。

void loadDataFromDisk(void) { //loadDataFromDisk和rdbSave對應加載寫入
    // 記錄開始時間
    long long start = ustime();
    // AOF 持久化是否已打開
    if (server.aof_state == REDIS_AOF_ON) {
        // 嘗試載入 AOF 文件
        if (loadAppendOnlyFile(server.aof_filename) == REDIS_OK)
            // 打印載入信息,并計算載入耗時長度
            redisLog(REDIS_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);
    // AOF 持久化未打開
    } else {
        // 嘗試載入 RDB 文件
        if (rdbLoad(server.rdb_filename) == REDIS_OK) {
            // 打印載入信息,并計算載入耗時長度
            redisLog(REDIS_NOTICE,"DB loaded from disk: %.3f seconds",
                (float)(ustime()-start)/1000000);
        } else if (errno != ENOENT) {
            redisLog(REDIS_WARNING,"Fatal error loading the DB: %s. Exiting.",strerror(errno));
            exit(1);
        }
    }
}

若開啟使用的是AOF持久化,則調(diào)用loadAppendOnlyFile進行加載AOF文件。

int loadAppendOnlyFile(char *filename) { 
    //偽客戶端
    struct redisClient *fakeClient; 
     // 打開 AOF 文件
    FILE *fp = fopen(filename,"r");、
    struct redis_stat sb;
    int old_aof_state = server.aof_state;
    long loops = 0;


     // 檢查文件的正確性
    if (fp && redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) {
        server.aof_current_size = 0;
        fclose(fp);
        return REDIS_ERR;
    }
     // 檢查文件是否正常打開
    if (fp == NULL) {
        redisLog(REDIS_WARNING,"Fatal error: can't open the append log file for reading: %s",strerror(errno));
        exit(1);
    }
    /* Temporarily disable AOF, to prevent EXEC from feeding a MULTI
     * to the same file we're about to read. 
     * 暫時性地關閉 AOF ,防止在執(zhí)行 MULTI 時,     
     * EXEC 命令被傳播到正在打開的 AOF 文件中。
     */
    server.aof_state = REDIS_AOF_OFF;
    //創(chuàng)建一個偽客戶端
    fakeClient = createFakeClient();
    // 設置服務器的狀態(tài)為:正在載入    
    startLoading(fp); //startLoading 定義于 rdb.c


    while(1) {
        int argc, j;
        unsigned long len;
        robj **argv;
        char buf[128];
        sds argsds;
        struct redisCommand *cmd;


        /* Serve the clients from time to time 
         * 間隔性地處理客戶端發(fā)送來的請求         
         * 因為服務器正處于載入狀態(tài),所以能正常執(zhí)行的只有 PUBSUB 等模塊
         */
        if (!(loops++ % 1000)) {
            loadingProgress(ftello(fp));
            processEventsWhileBlocked();
        }
         // 讀入文件內(nèi)容到緩存
        if (fgets(buf,sizeof(buf),fp) == NULL) {
            if (feof(fp))
                break; // 文件已經(jīng)讀完,跳出
            else
                goto readerr;
        }


        // 確認協(xié)議格式,比如 *3rn
        if (buf[0] != '*') goto fmterr;
        // 取出命令參數(shù),比如 *3rn 中的 3
        argc = atoi(buf+1);
       // 至少要有一個參數(shù)(被調(diào)用的命令)
        if (argc < 1) goto fmterr;
        // 從文本中創(chuàng)建字符串對象:包括命令,以及命令參數(shù)        
        // 例如 $3rnSETrn$3rnKEYrn$5rnVALUErn       
        // 將創(chuàng)建三個包含以下內(nèi)容的字符串對象:
        // SET 、 KEY 、 VALUE
        argv = zmalloc(sizeof(robj*)*argc);
        for (j = 0; j < argc; j++) {
            if (fgets(buf,sizeof(buf),fp) == NULL) goto readerr;


            if (buf[0] != '$') goto fmterr;


             // 讀取參數(shù)值的長度
            len = strtol(buf+1,NULL,10);
            // 讀取參數(shù)值            
            argsds = sdsnewlen(NULL,len);            
            if (len && fread(argsds,len,1,fp) == 0)
                goto fmterr;            


            // 為參數(shù)創(chuàng)建對象
            argv[j] = createObject(REDIS_STRING,argsds);


            if (fread(buf,2,1,fp) == 0) goto fmterr; /* discard CRLF */
        }


        /* Command lookup 
         *查找命令
         */
        cmd = lookupCommand(argv[0]->ptr);
        if (!cmd) {
            redisLog(REDIS_WARNING,"Unknown command '%s' reading the append only file", (char*)argv[0]->ptr);
            exit(1);
        }


        /* Run the command in the context of a fake client 
         * 調(diào)用偽客戶端,執(zhí)行命令
         */
        fakeClient->argc = argc;
        fakeClient->argv = argv;
        cmd->proc(fakeClient);


        /* The fake client should not have a reply */
        redisAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0);
        /* The fake client should never get blocked */
        redisAssert((fakeClient->flags & REDIS_BLOCKED) == 0);


        /* Clean up. Command code may have changed argv/argc so we use the
         * argv/argc of the client instead of the local variables. 
         * 清理命令和命令參數(shù)對象
         */
        for (j = 0; j < fakeClient->argc; j++)
            decrRefCount(fakeClient->argv[j]);
        zfree(fakeClient->argv);
    }


    /* This point can only be reached when EOF is reached without errors.
     * If the client is in the middle of a MULTI/EXEC, log error and quit. 
     * 如果能執(zhí)行到這里,說明 AOF 文件的全部內(nèi)容都可以正確地讀取,     
     * 但是,還要檢查 AOF 是否包含未正確結束的事務
     */
    if (fakeClient->flags & REDIS_MULTI) goto readerr;


    fclose(fp);
   // 釋放偽客戶端
    freeFakeClient(fakeClient);
   // 復原 AOF 狀態(tài)
    server.aof_state = old_aof_state;
   // 停止載入
    stopLoading();
    // 更新服務器狀態(tài)中, AOF 文件的當前大小
    aofUpdateCurrentSize();
    // 記錄前一次重寫時的大小
    server.aof_rewrite_base_size = server.aof_current_size;
    return REDIS_OK;


// 讀入錯誤
readerr:
    // 非預期的末尾,可能是 AOF 文件在寫入的中途遭遇了停機
    if (feof(fp)) {
        redisLog(REDIS_WARNING,"Unexpected end of file reading the append only file");
     // 文件內(nèi)容出錯
    } else {
        redisLog(REDIS_WARNING,"Unrecoverable error reading the append only file: %s", strerror(errno));
    }
    exit(1);
// 內(nèi)容格式錯誤
fmterr:
    redisLog(REDIS_WARNING,"Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>");
    exit(1);
}

由于AOF文件中保存的都是用戶可見的一條條命令,因此loadAppendOnlyFile通過創(chuàng)建一個偽客戶端讀取AOF文件獲取一條條命令來恢復執(zhí)行。

 

redis讀取AOF文件并還原數(shù)據(jù)庫的狀態(tài)過程如下:

 

以上就是服務器根據(jù)讀入AOF文件進行還原數(shù)據(jù)庫狀態(tài)的原理。

 

從上面的介紹可以知道加載AOF文件是redis啟動過程中的步驟,當業(yè)務線程啟動連接redis獲取或者保存數(shù)據(jù)時,若redis正在加載數(shù)據(jù),則有可能獲取不到數(shù)據(jù):

127.0.0.1:6379> get a
(error) LOADING Redis is loading the dataset in memory

原因是當加載過程中,有些客戶端發(fā)來的命令是不執(zhí)行的,直接返回上述結果。只有命令帶有"l"(小寫的L)標志的才會執(zhí)行,比如info命令等。

 

那么怎么判斷AOF文件是否加載完成呢?

剛才說到加載時可以執(zhí)行info命令,redis info 命令以一種易于理解和閱讀的格式,返回關于 Redis 服務器的各種信息和統(tǒng)計數(shù)值。該統(tǒng)計信息中有Persistence 參數(shù),該參數(shù)中記錄著 RDB 和 AOF 的相關信息,其中有個loading字段標志這數(shù)據(jù)是否正在加載,當正在加載時為1,加載完為0,因此可以根據(jù)該字段來判斷數(shù)據(jù)是否加載完畢。

127.0.0.1:6379> info Persistence
# Persistence
loading:1    // 1表示正在加載數(shù)據(jù)
...


127.0.0.1:6379> info Persistence
# Persistence
loading:0   // 0表示數(shù)據(jù)加載完畢
...

 

因此業(yè)務線程可以根據(jù)info命令獲取loading進行判斷數(shù)據(jù)是否加載完畢。

public boolean isLoading(){
  try{
        String info = (String)redisTemplate.getRequiredConnectionFactory().getConnection()
                      .info("Persistence").get("loading");
        if("0".equalsIgnoreCase(info)){
          return true;
        }
  }catch (Exception e){
      error("Failed to get loading status : {}", e.getMessage());
  }
  return false;
}

 

分享到:
標簽:redis
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定