在线观看不卡亚洲电影_亚洲妓女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 服務器存儲 session
消息中間件
Redis 與 Lua 腳本
什么樣的源代碼適合閱讀
Redis 數(shù)據(jù)結(jié)構(gòu) sds
Memcached slab 分配策略
訂閱發(fā)布機制
Redis 是如何提供服務的
Redis 事務機制
Redis 集群(下)
主從復制
Redis 應用
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é)合,也允許兩者同時關閉。

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

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

為什么稱為 append only file 呢?AOF 持久化是類似于生成一個關于 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)存中,當然保存到內(nèi)存中就不是持久化了。

RDB 持久化的運作機制

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

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

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

這里主要展開的內(nèi)容是 RDB 持久化操作的寫文件過程,讀過程和寫過程相反。子進程的產(chǎn)生發(fā)生在 rdbSaveBackground() 中,真正的 RDB 持久化操作是在 rdbSave(),想要直接進行 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;
    // 打開文件,準備寫
    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:
    // 清理工作,關閉文件描述符等
    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ù),其實就是子進程所消耗的內(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));
    }
}
    // 退出子進程
    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);
    // 子進程ID
    server.rdb_child_pid = childpid;
    updateDictResizePolicy();
    return REDIS_OK;
    }
    return REDIS_OK; /* unreached */
}

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

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

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ù)組織方式的細則感興趣,可以參看 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 中有關于每種數(shù)據(jù)的編碼和解碼的代碼,感興趣的同學可以詳細研讀一下。