在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ 大數(shù)據(jù)/ RDB 持久化策略
Redis 數(shù)據(jù)淘汰機制
積分排行榜
小剖 Memcache
Redis 數(shù)據(jù)結(jié)構(gòu) intset
分布式鎖
從哪里開始讀起,怎么讀
Redis 數(shù)據(jù)結(jié)構(gòu) dict
不在浮沙筑高臺
Redis 集群(上)
Redis 監(jiān)視器
源碼閱讀工具
Redis 日志和斷言
內(nèi)存數(shù)據(jù)管理
Redis 數(shù)據(jù)結(jié)構(gòu)綜述
源碼日志
Web 服務(wù)器存儲 session
消息中間件
Redis 與 Lua 腳本
什么樣的源代碼適合閱讀
Redis 數(shù)據(jù)結(jié)構(gòu) sds
Memcached slab 分配策略
訂閱發(fā)布機制
Redis 是如何提供服務(wù)的
Redis 事務(wù)機制
Redis 集群(下)
主從復(fù)制
Redis 應(yīng)用
RDB 持久化策略
Redis 數(shù)據(jù)遷移
Redis 事件驅(qū)動詳解
初探 Redis
Redis 與 Memcache
AOF 持久化策略
Redis 數(shù)據(jù)結(jié)構(gòu) redisOb
作者簡介
Redis 數(shù)據(jù)結(jié)構(gòu) ziplist
Redis 數(shù)據(jù)結(jié)構(gòu) skiplist
Redis 哨兵機制

RDB 持久化策略

簡介 Redis 持久化 RDB、AOF

為防止數(shù)據(jù)丟失,需要將 Redis 中的數(shù)據(jù)從內(nèi)存中 dump 到磁盤,這就是持久化。Redis 提供兩種持久化方式:RDB 和 AOF。Redis 允許兩者結(jié)合,也允許兩者同時關(guān)閉。

RDB 可以定時備份內(nèi)存中的數(shù)據(jù)集。服務(wù)器啟動的時候,可以從 RDB 文件中恢復(fù)數(shù)據(jù)集。

AOF(append only file) 可以記錄服務(wù)器的所有寫操作。在服務(wù)器重新啟動的時候,會把所有的寫操作重新執(zhí)行一遍,從而實現(xiàn)數(shù)據(jù)備份。當(dāng)寫操作集過大(比原有的數(shù)據(jù)集還大),Redis 會重寫寫操作集。

為什么稱為 append only file 呢?AOF 持久化是類似于生成一個關(guān)于 Redis 寫操作的文件,寫操作(增刪)總是以追加的方式追加到文件中。

本篇主要講的是 RDB 持久化,了解 RDB 的數(shù)據(jù)保存結(jié)構(gòu)和運作機制。Redis 主要在 rdb.h 和 rdb.c 兩個文件中實現(xiàn) RDB 的操作。

數(shù)據(jù)結(jié)構(gòu) rio

持久化的 IO 操作在 rio.h 和 rio.c 中實現(xiàn),核心數(shù)據(jù)結(jié)構(gòu)是 struct rio。RDB 中的幾乎每一個函數(shù)都帶有 rio 參數(shù)。struct rio 既適用于文件,又適用于內(nèi)存緩存,從 struct rio 的實現(xiàn)可見一斑,它抽象了文件和內(nèi)存的操作。

struct _rio {
// 函數(shù)指針,包括讀操作,寫操作和文件指針移動操作
/* Backend functions.
* Since this functions do not tolerate short writes or reads the return
* value is simplified to: zero on error, non zero on complete success. */
size_t (*read)(struct _rio *, void *buf, size_t len);
size_t (*write)(struct _rio *, const void *buf, size_t len);
off_t (*tell)(struct _rio *);
// 校驗和計算函數(shù)
/* The update_cksum method if not NULL is used to compute the checksum of
* all the data that was read or written so far. The method should be
* designed so that can be called with the current checksum, and the buf
* and len fields pointing to the new block of data to add to the checksum
* computation. */
void (*update_cksum)(struct _rio *, const void *buf, size_t len);
// 校驗和
/* The current checksum */
uint64_t cksum;
// 已經(jīng)讀取或者寫入的字符數(shù)
/* number of bytes read or written */
size_t processed_bytes;
// 每次最多能處理的字符數(shù)
/* maximum single read or write chunk size */
size_t max_processing_chunk;
// 可以是一個內(nèi)存總的字符串,也可以是一個文件描述符
/* Backend-specific vars. */
union {
struct {
sds ptr;
// 偏移量
off_t pos;
} buffer;
struct {
FILE *fp;
// 偏移量
off_t buffered; /* Bytes written since last fsync. */
off_t autosync; /* fsync after 'autosync' bytes written. */
} file;
} io;
};
typedef struct _rio rio;

redis 定義兩個 struct rio,分別是 rioFileIO 和 rioBufferIO,前者用于內(nèi)存緩存,后者用于文件 IO:

// 適用于內(nèi)存緩存
static const rio rioBufferIO = {
    rioBufferRead,
    rioBufferWrite,
    rioBufferTell,
    NULL, /* update_checksum */
    0, /* current checksum */
    0, /* bytes read or written */
    0, /* read/write chunk size */
    { { NULL, 0 } } /* union for io-specific vars */
};
// 適用于文件IO
static const rio rioFileIO = {
    rioFileRead,
    rioFileWrite,
    rioFileTell,
    NULL, /* update_checksum */
    0, /* current checksum */
    0, /* bytes read or written */
    0, /* read/write chunk size */
    { { NULL, 0 } } /* union for io-specific vars */
};

因此,在 RDB 持久化的時候可以將 RDB 保存到磁盤中,也可以保存到內(nèi)存中,當(dāng)然保存到內(nèi)存中就不是持久化了。

RDB 持久化的運作機制

http://wiki.jikexueyuan.com/project/redis/images/redis16.png" alt="" />

Redis 支持兩種方式進(jìn)行 RDB 持久化:當(dāng)前進(jìn)程執(zhí)行和后臺執(zhí)行(BGSAVE)。RDB BGSAVE 策略是 fork 出一個子進(jìn)程,把內(nèi)存中的數(shù)據(jù)集整個 dump 到硬盤上。兩個場景舉例:

  1. Redis 服務(wù)器初始化過程中,設(shè)定了定時事件,每隔一段時間就會觸發(fā)持久化操作; 進(jìn)入定時事件處理程序中,就會 fork 產(chǎn)生子進(jìn)程執(zhí)行持久化操作。
  2. Redis 服務(wù)器預(yù)設(shè)了 save 指令,客戶端可要求服務(wù)器進(jìn)程中斷服務(wù),執(zhí)行持久化操作。

這里主要展開的內(nèi)容是 RDB 持久化操作的寫文件過程,讀過程和寫過程相反。子進(jìn)程的產(chǎn)生發(fā)生在 rdbSaveBackground() 中,真正的 RDB 持久化操作是在 rdbSave(),想要直接進(jìn)行 RDB 持久化,調(diào)用 rdbSave() 即可。

以下主要以代碼的方式來展開 RDB 的運作機制:

// 備份主程序
/* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success */
    int rdbSave(char *filename) {
    dictIterator *di = NULL;
    dictEntry *de;
    char tmpfile[256];
    char magic[10];
    int j;
    long long now = mstime();
    FILE *fp;
    rio rdb;
    uint64_t cksum;
    // 打開文件,準(zhǔn)備寫
    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;
    }
    // 初始化rdb 結(jié)構(gòu)體。rdb 結(jié)構(gòu)體內(nèi)指定了讀寫文件的函數(shù),已寫/讀字符統(tǒng)計等數(shù)據(jù)
        rioInitWithFile(&rdb,fp);
    if (server.rdb_checksum) // 校驗和
        rdb.update_cksum = rioGenericUpdateChecksum;
    // 先寫入版本號
        snprintf(magic,sizeof(magic),"REDIS%04d",REDIS_RDB_VERSION);
    if (rdbWriteRaw(&rdb,magic,9) == -1) goto werr;
    for (j = 0; j < server.dbnum; j++) {
    // server 中保存的數(shù)據(jù)
        redisDb *db = server.db+j;
    // 字典
        dict *d = db->dict;
    if (dictSize(d) == 0) continue;
    // 字典迭代器
        di = dictGetSafeIterator(d);
    if (!di) {
        fclose(fp);
        return REDIS_ERR;
    }
    // 寫入RDB 操作碼
    /* Write the SELECT DB opcode */
    if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_SELECTDB) == -1) goto werr;
    // 寫入數(shù)據(jù)庫序號
    if (rdbSaveLen(&rdb,j) == -1) goto werr;
    // 寫入數(shù)據(jù)庫中每一個數(shù)據(jù)項
    /* Iterate this DB writing every entry */
    while((de = dictNext(di)) != NULL) {
        sds keystr = dictGetKey(de);
        robj key,
        *o = dictGetVal(de);
        long long expire;
        // 將keystr 封裝在robj 里
        initStaticStringObject(key,keystr);
        // 獲取過期時間
        expire = getExpire(db,&key);
        // 開始寫入磁盤
    if (rdbSaveKeyValuePair(&rdb,&key,o,expire,now) == -1) goto werr;
    }
        dictReleaseIterator(di);
    }
    di = NULL; /* So that we don't release it again on error. */
    // RDB 結(jié)束碼
    /* EOF opcode */
    if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_EOF) == -1) goto werr;
    // 校驗和
    /* CRC64 checksum. It will be zero if checksum computation is disabled, the
    * loading code skips the check in this case. */
        cksum = rdb.cksum;
        memrev64ifbe(&cksum);
        rioWrite(&rdb,&cksum,8);
    // 同步到磁盤
    /* Make sure data will not remain on the OS's output buffers */
        fflush(fp);
        fsync(fileno(fp));
        fclose(fp);
    // 修改臨時文件名為指定文件名
    /* Use RENAME to make sure the DB file is changed atomically only
    * if the generate DB file is ok. */
    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;
    }
    redisLog(REDIS_NOTICE,"DB saved on disk");
    server.dirty = 0;
    // 記錄成功執(zhí)行保存的時間
    server.lastsave = time(NULL);
    // 記錄執(zhí)行的結(jié)果狀態(tài)為成功
    server.lastbgsave_status = REDIS_OK;
    return REDIS_OK;
    werr:
    // 清理工作,關(guān)閉文件描述符等
    fclose(fp);
    unlink(tmpfile);
    redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));
    if (di) dictReleaseIterator(di);
        return REDIS_ERR;
    }
    // bgsaveCommand(),serverCron(),syncCommand(),updateSlavesWaitingBgsave() 會調(diào)用
    // rdbSaveBackground()
    int rdbSaveBackground(char *filename) {
    pid_t childpid;
    long long start;
    // 已經(jīng)有后臺程序了,拒絕再次執(zhí)行
    if (server.rdb_child_pid != -1) return REDIS_ERR;
    server.dirty_before_bgsave = server.dirty;
    // 記錄這次嘗試執(zhí)行持久化操作的時間
    server.lastbgsave_try = time(NULL);
    start = ustime();
    if ((childpid = fork()) == 0) {
        int retval;
    // 取消監(jiān)聽
    /* Child */
        closeListeningSockets(0);
        redisSetProcTitle("redis-rdb-bgsave");
        // 執(zhí)行備份主程序
        retval = rdbSave(filename);
        // 臟數(shù)據(jù),其實就是子進(jìn)程所消耗的內(nèi)存大小
    if (retval == REDIS_OK) {
        // 獲取臟數(shù)據(jù)大小
        size_t private_dirty = zmalloc_get_private_dirty();
        // 記錄臟數(shù)據(jù)
    if (private_dirty) {
        redisLog(REDIS_NOTICE,
        "RDB: %zu MB of memory used by copy-on-write",
        private_dirty/(1024*1024));
    }
}
    // 退出子進(jìn)程
    exitFromChild((retval == REDIS_OK) ? 0 : 1);
    } else {
    /* Parent */
    // 計算fork 消耗的時間
        server.stat_fork_time = ustime()-start;
        // fork 出錯
    if (childpid == -1) {
        // 記錄執(zhí)行的結(jié)果狀態(tài)為失敗
        server.lastbgsave_status = REDIS_ERR;
        redisLog(REDIS_WARNING,"Can't save in background: fork: %s",
        strerror(errno));
        return REDIS_ERR;
    }
    redisLog(REDIS_NOTICE,"Background saving started by pid %d",childpid);
    // 記錄保存的起始時間
    server.rdb_save_time_start = time(NULL);
    // 子進(jìn)程ID
    server.rdb_child_pid = childpid;
    updateDictResizePolicy();
    return REDIS_OK;
    }
    return REDIS_OK; /* unreached */
}

如果采用 BGSAVE 策略,且內(nèi)存中的數(shù)據(jù)集很大,fork() 會因為要為子進(jìn)程產(chǎn)生一份虛擬空間表而花費較長的時間;如果此時客戶端請求數(shù)量非常大的話,會導(dǎo)致較多的寫時拷貝操作;在 RDB 持久化操作過程中,每一個數(shù)據(jù)都會導(dǎo)致 write() 系統(tǒng)調(diào)用,CPU 資源很緊張。因此,如果在一臺物理機上部署多個 Redis,應(yīng)該避免同時持久化操作。

那如何知道 BGSAVE 占用了多少內(nèi)存?子進(jìn)程在結(jié)束之前,讀取了自身私有臟數(shù)據(jù) Private_Dirty 的大小,這樣做是為了讓用戶看到 Redis 的持久化進(jìn)程所占用了有多少的空間。在父進(jìn)程 fork 產(chǎn)生子進(jìn)程過后,父子進(jìn)程雖然有不同的虛擬空間,但物理空間上是共存的,直至父進(jìn)程或者子進(jìn)程修改內(nèi)存數(shù)據(jù)為止,所以臟數(shù) Private_Dirty 可以近似的認(rèn)為是子進(jìn)程,即持久化進(jìn)程占用的空間。

RDB 數(shù)據(jù)的組織方式

RDB 的文件組織方式為:數(shù)據(jù)集序號1:操作碼:數(shù)據(jù)1:結(jié)束碼:校驗和—-數(shù)據(jù)集序號2:操作碼:數(shù)據(jù)2:結(jié)束碼:校驗和……

其中,數(shù)據(jù)的組織方式為:過期時間:數(shù)據(jù)類型:鍵:值,即 TVL(type,length,value)。

舉兩個字符串存儲的例子,其他的大概都以至于的形式來組織數(shù)據(jù):

http://wiki.jikexueyuan.com/project/redis/images/redis17.png" alt="" />

可見,RDB 持久化的結(jié)果是一個非常緊湊的文件,幾乎每一位都是有用的信息。如果對 redis RDB 數(shù)據(jù)組織方式的細(xì)則感興趣,可以參看 rdb.h 和 rdb.c 兩個文件的實現(xiàn)。

對于每一個鍵值對都會調(diào)用 rdbSaveKeyValuePair(),如下:

int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val,
long long expiretime, long long now)
{
// 過期時間
/* Save the expire time */
if (expiretime != -1) {
/* If this key is already expired skip it */
if (expiretime < now) return 0;
if (rdbSaveType(rdb,REDIS_RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;
if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;
}
/* Save type, key, value */
// 數(shù)據(jù)類型
if (rdbSaveObjectType(rdb,val) == -1) return -1;
// 鍵
if (rdbSaveStringObject(rdb,key) == -1) return -1;
// 值
if (rdbSaveObject(rdb,val) == -1) return -1;
return 1;
}

在 rdb.h, rdb.c 中有關(guān)于每種數(shù)據(jù)的編碼和解碼的代碼,感興趣的同學(xué)可以詳細(xì)研讀一下。