持久化
rdb是redis的一種持久化的方案徒探,他每隔一段時(shí)間將redisdb里面的數(shù)據(jù)序列化到硬盤中保存苞氮。所以喃序列化就是關(guān)鍵。
在Redisdb里面保存的都是key value數(shù)據(jù) . 序列化的話我們其實(shí)就是key value 對(duì)于key來說保存的是string的對(duì)象,可以直接存儲(chǔ)荔仁,對(duì)于value來說,是一個(gè)redisobjet 這個(gè)就是有很多的類型了,可以是string可以是hash乏梁,可以是set次洼。所以先來看下對(duì)于Redisobject的序列化。
RedisObject的序列化
我們都知道我們的RedisObject里面可以存儲(chǔ)很多不同類型的變量遇骑。所以我們序列化有一點(diǎn)是必須的卖毁,那就是我們要保存類型。沒有類型靠?jī)?nèi)存二進(jìn)制反向出來內(nèi)容落萎,操作性基本不大現(xiàn)實(shí)亥啦,所以先來看下類型的定義
#define RDB_TYPE_STRING 0
#define RDB_TYPE_LIST 1
#define RDB_TYPE_SET 2
#define RDB_TYPE_ZSET 3
#define RDB_TYPE_HASH 4
#define RDB_TYPE_ZSET_2 5 /* ZSET version 2 with doubles stored in binary. */
#define RDB_TYPE_MODULE 6
#define RDB_TYPE_MODULE_2 7 /* Module value with annotations for parsing without */
#define RDB_TYPE_HASH_ZIPMAP 9
#define RDB_TYPE_LIST_ZIPLIST 10
#define RDB_TYPE_SET_INTSET 11
#define RDB_TYPE_ZSET_ZIPLIST 12
#define RDB_TYPE_HASH_ZIPLIST 13
#define RDB_TYPE_LIST_QUICKLIST 14
#define rdbIsObjectType(t) ((t >= 0 && t <= 7) || (t >= 9 && t <= 14))
#define RDB_ENC_INT8 0 /* 8 bit signed integer */
#define RDB_ENC_INT16 1 /* 16 bit signed integer */
#define RDB_ENC_INT32 2 /* 32 bit signed integer */
#define RDB_ENC_LZF 3 /* string compressed with FASTLZ */
這個(gè)定義賊簡(jiǎn)潔明了就不多說了看名字就看出來了
再來看下對(duì)類型的保存
int rdbSaveObjectType(rio *rdb, robj *o) {
switch (o->type) {//先獲取主類型
case OBJ_STRING://string
return rdbSaveType(rdb,RDB_TYPE_STRING);//保存成string
case OBJ_LIST:
if (o->encoding == OBJ_ENCODING_QUICKLIST)//quicklist
return rdbSaveType(rdb,RDB_TYPE_LIST_QUICKLIST);
else
serverPanic("Unknown list encoding");
case OBJ_SET://set 有兩種保存方式 intset 和 hashtable
if (o->encoding == OBJ_ENCODING_INTSET)//
return rdbSaveType(rdb,RDB_TYPE_SET_INTSET);
else if (o->encoding == OBJ_ENCODING_HT)
return rdbSaveType(rdb,RDB_TYPE_SET);
else
serverPanic("Unknown set encoding");
case OBJ_ZSET://zet 也有兩種形式 ziplist 和 skiplist
if (o->encoding == OBJ_ENCODING_ZIPLIST)
return rdbSaveType(rdb,RDB_TYPE_ZSET_ZIPLIST);
else if (o->encoding == OBJ_ENCODING_SKIPLIST)
return rdbSaveType(rdb,RDB_TYPE_ZSET_2);
else
serverPanic("Unknown sorted set encoding");
case OBJ_HASH://hash table 也是兩種 ziplist hashtable
if (o->encoding == OBJ_ENCODING_ZIPLIST)
return rdbSaveType(rdb,RDB_TYPE_HASH_ZIPLIST);
else if (o->encoding == OBJ_ENCODING_HT)
return rdbSaveType(rdb,RDB_TYPE_HASH);
else
serverPanic("Unknown hash encoding");
case OBJ_MODULE:
return rdbSaveType(rdb,RDB_TYPE_MODULE_2);
default:
serverPanic("Unknown object type");
}
return -1; /* avoid warning */
}
保存完類型之后我們需要的是保存的是我們的value。 我們的value有很多很多類型模暗。對(duì)于我們的value來說不管是string hashtable list這些都有一個(gè)長(zhǎng)度在里面禁悠,我們需要知道我們保持的內(nèi)容有多少。
這個(gè)長(zhǎng)度對(duì)于不同的個(gè)體來說代表的意義不一樣兑宇。比如對(duì)于string肯定是字符才長(zhǎng)度碍侦,對(duì)于hashtable肯定是鍵值對(duì)的個(gè)數(shù)。
現(xiàn)在來看對(duì)len的編碼隶糕,其實(shí)我們對(duì)于編碼還是見過很多了ziplist那些其實(shí)都是大同小異的過一下就好
#define RDB_6BITLEN 0 //六位保存
#define RDB_14BITLEN 1 //十四位保存
#define RDB_32BITLEN 0x80 //4字節(jié)保存
#define RDB_64BITLEN 0x81 //8字節(jié)保存
#define RDB_ENCVAL 3 //保存的是數(shù)字
#define RDB_LENERR UINT64_MAX
//保存長(zhǎng)度
int rdbSaveLen(rio *rdb, uint64_t len) {
unsigned char buf[2];
size_t nwritten;
if (len < (1<<6)) {
/* Save a 6 bit len */
buf[0] = (len&0xFF)|(RDB_6BITLEN<<6); //高兩位為00
if (rdbWriteRaw(rdb,buf,1) == -1) return -1;
nwritten = 1;
} else if (len < (1<<14)) {
/* Save a 14 bit len */
buf[0] = ((len>>8)&0xFF)|(RDB_14BITLEN<<6);//高兩位為01
buf[1] = len&0xFF;
if (rdbWriteRaw(rdb,buf,2) == -1) return -1;
nwritten = 2;
} else if (len <= UINT32_MAX) {
/* Save a 32 bit len */
buf[0] = RDB_32BITLEN;//高兩位為10
if (rdbWriteRaw(rdb,buf,1) == -1) return -1;
uint32_t len32 = htonl(len);
if (rdbWriteRaw(rdb,&len32,4) == -1) return -1;
nwritten = 1+4;
} else {
/* Save a 64 bit len */
buf[0] = RDB_64BITLEN;//高兩位為10 最后一位為1
if (rdbWriteRaw(rdb,buf,1) == -1) return -1;
len = htonu64(len);
if (rdbWriteRaw(rdb,&len,8) == -1) return -1;
nwritten = 1+8;
}
return nwritten;
}
//保存數(shù)字
int rdbEncodeInteger(long long value, unsigned char *enc) { //高兩位做為int的標(biāo)致位
if (value >= -(1<<7) && value <= (1<<7)-1) {//一位可以保存
enc[0] = (RDB_ENCVAL<<6)|RDB_ENC_INT8; // 高兩位為11
enc[1] = value&0xFF;//
return 2;
} else if (value >= -(1<<15) && value <= (1<<15)-1) {//兩位可以保存
enc[0] = (RDB_ENCVAL<<6)|RDB_ENC_INT16;
enc[1] = value&0xFF;
enc[2] = (value>>8)&0xFF;
return 3;
} else if (value >= -((long long)1<<31) && value <= ((long long)1<<31)-1) {//四位保存
enc[0] = (RDB_ENCVAL<<6)|RDB_ENC_INT32;
enc[1] = value&0xFF;
enc[2] = (value>>8)&0xFF;
enc[3] = (value>>16)&0xFF;
enc[4] = (value>>24)&0xFF;
return 5;
} else {
return 0;
}
}
對(duì)于長(zhǎng)度來說這是一個(gè)數(shù)字瓷产,在后面我們會(huì)發(fā)現(xiàn)在保存字符串的時(shí)候,如果我們的字符串可以轉(zhuǎn)換成一個(gè)數(shù)字的話會(huì)保存成一個(gè)數(shù)字枚驻。因?yàn)檫@樣子可以節(jié)約空間
保存是數(shù)字還是長(zhǎng)度的區(qū)別在于第一個(gè)字符的高兩位
高兩位為11 這時(shí)候就是代表是數(shù)字
來看下具體的獲取數(shù)字
//獲取保存數(shù)字的長(zhǎng)度和保存的是一個(gè)單純的數(shù)字還是一個(gè)長(zhǎng)度
//isencoded 返回代表是一個(gè)數(shù)字還是長(zhǎng)度
//lenptr 在isencoded為0會(huì)讀取具的長(zhǎng)度當(dāng)為1的時(shí)候沒有讀取具體的值只是讀取值保存的長(zhǎng)度
int rdbLoadLenByRef(rio *rdb, int *isencoded, uint64_t *lenptr) {
unsigned char buf[2];
int type;
if (isencoded) *isencoded = 0;
if (rioRead(rdb,buf,1) == 0) return -1;
type = (buf[0]&0xC0)>>6; //獲取高兩位
if (type == RDB_ENCVAL) {
/* Read a 6 bit encoding type. */
if (isencoded) *isencoded = 1;//代表是返回的類型
*lenptr = buf[0]&0x3F; //獲取低六位 低六位保存的是類型
} else if (type == RDB_6BITLEN) {//高兩位為0 代表后六位為長(zhǎng)度
/* Read a 6 bit len. */
*lenptr = buf[0]&0x3F;//拉取長(zhǎng)度
} else if (type == RDB_14BITLEN) {//高兩位為01 代表后十四位位長(zhǎng)度
/* Read a 14 bit len. */
if (rioRead(rdb,buf+1,1) == 0) return -1;
*lenptr = ((buf[0]&0x3F)<<8)|buf[1];
} else if (buf[0] == RDB_32BITLEN) {// 判斷是32位的類型
/* Read a 32 bit len. */
uint32_t len;
if (rioRead(rdb,&len,4) == 0) return -1;//讀取四位
*lenptr = ntohl(len);//獲取長(zhǎng)度
} else if (buf[0] == RDB_64BITLEN) {//64位類型
/* Read a 64 bit len. */
uint64_t len;
if (rioRead(rdb,&len,8) == 0) return -1;//讀取八位
*lenptr = ntohu64(len);
} else {//錯(cuò)誤的類型
rdbExitReportCorruptRDB(
"Unknown length encoding %d in rdbLoadLen()",type);
return -1; /* Never reached. */
}
return 0;
}
在長(zhǎng)度的獲取中我們首先進(jìn)行的是高兩位的校驗(yàn)
當(dāng)發(fā)現(xiàn)高兩位是RDB_ENCVAL他會(huì)設(shè)置標(biāo)識(shí)這是一個(gè)數(shù)字濒旦,并且lenptr也僅僅是保存了數(shù)字保存的長(zhǎng)度并沒有獲取具體的值
當(dāng)高兩位非RDB_ENCVAL會(huì)獲取出具體的len的值當(dāng)返回值為-1代表失敗0代表成功。
Save Object
在Redis保存對(duì)象的過程中他保存類型和保存值是分開的再登。他是先調(diào)用saveobjecttype先把類型保存起來之后尔邓,再調(diào)用saveobject來保存值。所以我們后面來直接看值的保存锉矢。
SaveString
對(duì)于string來說梯嗽,保存存在三種情況。
1.這個(gè)string是否是一個(gè)數(shù)字或者是否能夠被轉(zhuǎn)換成一個(gè)數(shù)字來進(jìn)行保存沽损。
2這個(gè)string是否長(zhǎng)灯节,是否打開了壓縮標(biāo)記。如果是就進(jìn)行壓縮保存绵估。
3這個(gè)string進(jìn)行一個(gè)普通的字符串保存炎疆。
第一種是數(shù)字進(jìn)行保存
ssize_t rdbSaveLongLongAsStringObject(rio *rdb, long long value) {
unsigned char buf[32];
ssize_t n, nwritten = 0;
int enclen = rdbEncodeInteger(value,buf);//對(duì)數(shù)字進(jìn)行編碼
if (enclen > 0) {//編碼成功
return rdbWriteRaw(rdb,buf,enclen);//直接寫入buf里面的內(nèi)容
} else {
/* Encode as string */
enclen = ll2string((char*)buf,32,value);//轉(zhuǎn)換才string
serverAssert(enclen < 32);
if ((n = rdbSaveLen(rdb,enclen)) == -1) return -1;//先寫入len 返回花費(fèi)的長(zhǎng)度
nwritten += n;
if ((n = rdbWriteRaw(rdb,buf,enclen)) == -1) return -1;//寫入內(nèi)容
nwritten += n;
}
return nwritten;//返回寫入的長(zhǎng)度
}
第二中壓縮保存
ssize_t rdbSaveLzfStringObject(rio *rdb, unsigned char *s, size_t len) {
size_t comprlen, outlen;
void *out;
/* We require at least four bytes compression for this to be worth it */
if (len <= 4) return 0;
outlen = len-4;
if ((out = zmalloc(outlen+1)) == NULL) return 0;//分配壓縮后的空間
comprlen = lzf_compress(s, len, out, outlen);//壓縮
if (comprlen == 0) {//壓縮失敗
zfree(out);
return 0;
}
ssize_t nwritten = rdbSaveLzfBlob(rdb, out, comprlen, len);//寫入壓縮后的文件
zfree(out);
return nwritten;
}
//保存壓縮對(duì)象
//標(biāo)記位為RDB_ENCVAL<<6)|RDB_ENC_LZF;
//然后保存壓縮后長(zhǎng)度 然后寫入原來的長(zhǎng)度
//最后寫入壓縮后的二進(jìn)制 長(zhǎng)度為壓縮后的長(zhǎng)度
ssize_t rdbSaveLzfBlob(rio *rdb, void *data, size_t compress_len,
size_t original_len) {
unsigned char byte;
ssize_t n, nwritten = 0;
/* Data compressed! Let's save it on disk */
byte = (RDB_ENCVAL<<6)|RDB_ENC_LZF; //保存格式 壓縮標(biāo)記高兩位也是用的11 低兩位使用的11 在loadlen 代碼可以參考
if ((n = rdbWriteRaw(rdb,&byte,1)) == -1) goto writeerr;//首先寫入壓縮標(biāo)記
nwritten += n;
if ((n = rdbSaveLen(rdb,compress_len)) == -1) goto writeerr;//寫入壓縮后的長(zhǎng)度
nwritten += n;
if ((n = rdbSaveLen(rdb,original_len)) == -1) goto writeerr;//寫入原來的長(zhǎng)度
nwritten += n;
if ((n = rdbWriteRaw(rdb,data,compress_len)) == -1) goto writeerr;//寫入壓縮后的二進(jìn)制
nwritten += n;
return nwritten;
writeerr:
return -1;
}
第三種保存普通的string對(duì)象
//沒有特別的操作 寫入長(zhǎng)度寫入二進(jìn)制
/* Store verbatim */
if ((n = rdbSaveLen(rdb,len)) == -1) return -1;//保存長(zhǎng)度
nwritten += n;
if (len > 0) {
if (rdbWriteRaw(rdb,s,len) == -1) return -1;//寫入二進(jìn)制
nwritten += len;
}
最后我們來看下string整體的保存和判斷保存類型流程
//首先保存入口
ssize_t rdbSaveStringObject(rio *rdb, robj *obj) {
/* Avoid to decode the object, then encode it again, if the
* object is already integer encoded. */
//首先判斷是不是一個(gè)數(shù)字
if (obj->encoding == OBJ_ENCODING_INT) {//這是一個(gè)int的值
return rdbSaveLongLongAsStringObject(rdb,(long)obj->ptr);
} else {
serverAssertWithInfo(NULL,obj,sdsEncodedObject(obj));
//寫入rawstring
return rdbSaveRawString(rdb,obj->ptr,sdslen(obj->ptr));//寫入
}
}
//保存數(shù)字的上面已經(jīng)有了 現(xiàn)在看下raw
/* Save a string object as [len][data] on disk. If the object is a string
* representation of an integer value we try to save it in a special form */
//保存rawstring
//在raw主要進(jìn)行了兩次判斷
//第一次判斷這個(gè)字符串能否轉(zhuǎn)換成一個(gè)數(shù)字進(jìn)行保存
//第二次判斷是判斷是否能夠壓縮然后進(jìn)行壓縮保存
//都不行的話才進(jìn)行普通的string保存
ssize_t rdbSaveRawString(rio *rdb, unsigned char *s, size_t len) {
int enclen;
ssize_t n, nwritten = 0;
/* Try integer encoding */
if (len <= 11) {//首先這個(gè)還是蠻小的
unsigned char buf[5];
if ((enclen = rdbTryIntegerEncoding((char*)s,len,buf)) > 0) {//嘗試轉(zhuǎn)換成int
if (rdbWriteRaw(rdb,buf,enclen) == -1) return -1;//如果是int buf已經(jīng)被編碼了 寫入就行了
return enclen;
}
}
/* Try LZF compression - under 20 bytes it's unable to compress even
* aaaaaaaaaaaaaaaaaa so skip it */
if (server.rdb_compression && len > 20) {//如果配置了壓縮選項(xiàng)
n = rdbSaveLzfStringObject(rdb,s,len);
if (n == -1) return -1;
if (n > 0) return n;
/* Return value of 0 means data can't be compressed, save the old way */
}
/* Store verbatim */
if ((n = rdbSaveLen(rdb,len)) == -1) return -1;//沒有壓縮標(biāo)記或長(zhǎng)度蠻短的 先寫入長(zhǎng)度
nwritten += n;
if (len > 0) {
if (rdbWriteRaw(rdb,s,len) == -1) return -1;//寫入二進(jìn)制
nwritten += len;
}
return nwritten;
}
其他Object的保存
ssize_t rdbSaveObject(rio *rdb, robj *o) { //save object
ssize_t n = 0, nwritten = 0;
if (o->type == OBJ_STRING) {//string
/* Save a string value */
if ((n = rdbSaveStringObject(rdb,o)) == -1) return -1;
nwritten += n;
} else if (o->type == OBJ_LIST) {//這個(gè)逗比是個(gè)list
/* Save a list value */
//對(duì)于我們的quicklist來說 他的每一個(gè)節(jié)點(diǎn)是一個(gè)quicklistnode 他的數(shù)據(jù)區(qū)域是使用的ziplist
//ziplist是一個(gè)連續(xù)的內(nèi)存塊 所以在為壓縮的node節(jié)點(diǎn)將將ziplist當(dāng)成一個(gè)string來保存
//如果是一個(gè)壓縮的節(jié)點(diǎn),使用blob的方式的保存
if (o->encoding == OBJ_ENCODING_QUICKLIST) {
quicklist *ql = o->ptr;
quicklistNode *node = ql->head;
if ((n = rdbSaveLen(rdb,ql->len)) == -1) return -1;//先寫入長(zhǎng)度
nwritten += n;
while(node) {
if (quicklistNodeIsCompressed(node)) {
void *data;
size_t compress_len = quicklistGetLzf(node, &data);//獲得壓縮后的長(zhǎng)度和壓縮的data
//使用壓縮字符串的保存方式
if ((n = rdbSaveLzfBlob(rdb,data,compress_len,node->sz)) == -1) return -1;//寫入標(biāo)記為壓縮信息
nwritten += n;
} else {
//將ziplist當(dāng)成是一個(gè)string直接保存內(nèi)存塊
if ((n = rdbSaveRawString(rdb,node->zl,node->sz)) == -1) return -1;//直接寫入
nwritten += n;
}
node = node->next;
}
} else {
serverPanic("Unknown list encoding");
}
} else if (o->type == OBJ_SET) {//寫入set
//對(duì)于set 來說 如果我們使用的是hashtable 那么我們只需要遍歷一遍set 把他的key全部寫入就好了
//如果是intset 這個(gè)玩意就簡(jiǎn)單了 因?yàn)樗且粋€(gè)連續(xù)的二進(jìn)制 當(dāng)成一個(gè)string寫入
/* Save a set value */
if (o->encoding == OBJ_ENCODING_HT) {//這玩意是hash
dict *set = o->ptr;//獲取dict
dictIterator *di = dictGetIterator(set);//獲取iter
dictEntry *de;
if ((n = rdbSaveLen(rdb,dictSize(set))) == -1) return -1;//保存長(zhǎng)度
nwritten += n;
while((de = dictNext(di)) != NULL) {//遍歷
sds ele = dictGetKey(de);//拉取key set只有key 具體可看set的保存方式
if ((n = rdbSaveRawString(rdb,(unsigned char*)ele,sdslen(ele)))//
== -1) return -1;
nwritten += n;
}
dictReleaseIterator(di);
} else if (o->encoding == OBJ_ENCODING_INTSET) {
size_t l = intsetBlobLen((intset*)o->ptr);//這個(gè)就很6了 直接把這個(gè)玩意當(dāng)成一個(gè)二進(jìn)制錘進(jìn)去
//當(dāng)成string 進(jìn)行存儲(chǔ)
if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
nwritten += n;
} else {
serverPanic("Unknown set encoding");
}
} else if (o->type == OBJ_ZSET) {
/* Save a sorted set value */
//第一種情況是一個(gè)ziplst 連續(xù)的內(nèi)存 直接把ziplist當(dāng)成一個(gè)string寫入
//第二章情況
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
size_t l = ziplistBlobLen((unsigned char*)o->ptr);//連續(xù)的內(nèi)存 編碼什么的 不存在的直接錘擊去
if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;//錘進(jìn)去
nwritten += n;
} else if (o->encoding == OBJ_ENCODING_SKIPLIST) {
zset *zs = o->ptr;
zskiplist *zsl = zs->zsl;
if ((n = rdbSaveLen(rdb,zsl->length)) == -1) return -1;//先寫入長(zhǎng)度
nwritten += n;
/* We save the skiplist elements from the greatest to the smallest
* (that's trivial since the elements are already ordered in the
* skiplist): this improves the load process, since the next loaded
* element will always be the smaller, so adding to the skiplist
* will always immediately stop at the head, making the insertion
* O(1) instead of O(log(N)). */
//這里將只會(huì)保存key和source信息 其他信息將會(huì)被拋棄
//其他信息可以在讀取的時(shí)候 重新購(gòu)照
zskiplistNode *zn = zsl->tail;
while (zn != NULL) {
if ((n = rdbSaveRawString(rdb,
(unsigned char*)zn->ele,sdslen(zn->ele))) == -1)//首先寫入key的信息
{
return -1;
}
nwritten += n;
if ((n = rdbSaveBinaryDoubleValue(rdb,zn->score)) == -1) //再寫入一個(gè)double信息 這里沒有進(jìn)行編碼 因?yàn)閗ey的長(zhǎng)度知道 在我們讀取的時(shí)候 將會(huì)是一個(gè)固定的讀取方式 先讀取出string之后 緊接著會(huì)讀取到一個(gè)double 如果不滿足說明是文件有問題
return -1;
nwritten += n;
zn = zn->backward;
}
} else {
serverPanic("Unknown sorted set encoding");
}
} else if (o->type == OBJ_HASH) {
/* Save a hash value */
//對(duì)于hash來說 ziplist 就直接當(dāng)成string寫入
//hashtable 獲取key value 挨著當(dāng)成string寫入
if (o->encoding == OBJ_ENCODING_ZIPLIST) { //ziplist
size_t l = ziplistBlobLen((unsigned char*)o->ptr);//連續(xù)內(nèi)容
if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;//直接錘進(jìn)去
nwritten += n;
} else if (o->encoding == OBJ_ENCODING_HT) {
dictIterator *di = dictGetIterator(o->ptr);
dictEntry *de;
if ((n = rdbSaveLen(rdb,dictSize((dict*)o->ptr))) == -1) return -1;//寫入長(zhǎng)度
nwritten += n;
while((de = dictNext(di)) != NULL) {
sds field = dictGetKey(de);//獲取key
sds value = dictGetVal(de);//獲取value
if ((n = rdbSaveRawString(rdb,(unsigned char*)field,
sdslen(field))) == -1) return -1;//錘入key
nwritten += n;
if ((n = rdbSaveRawString(rdb,(unsigned char*)value,
sdslen(value))) == -1) return -1; //錘入value
nwritten += n;
}
dictReleaseIterator(di);
} else {
serverPanic("Unknown hash encoding");
}
} else if (o->type == OBJ_MODULE) {
/* Save a module-specific value. */
RedisModuleIO io;
moduleValue *mv = o->ptr;
moduleType *mt = mv->type;
moduleInitIOContext(io,mt,rdb);
/* Write the "module" identifier as prefix, so that we'll be able
* to call the right module during loading. */
int retval = rdbSaveLen(rdb,mt->id);
if (retval == -1) return -1;
io.bytes += retval;
/* Then write the module-specific representation + EOF marker. */
mt->rdb_save(&io,mv->value);
retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_EOF);
if (retval == -1) return -1;
io.bytes += retval;
if (io.ctx) {
moduleFreeContext(io.ctx);
zfree(io.ctx);
}
return io.error ? -1 : (ssize_t)io.bytes;
} else {
serverPanic("Unknown object type");
}
return nwritten;
}
對(duì)對(duì)象的存儲(chǔ)可以總結(jié)下
首先存儲(chǔ)的是對(duì)象的類型国裳,對(duì)應(yīng)的是SaveObjetType方法這個(gè)方法其實(shí)就是把類型編號(hào)存儲(chǔ)文件形入。
然后存儲(chǔ)Object的大小對(duì)于大小這個(gè)類型來說類型不同是不一樣的。對(duì)于string來說將會(huì)存儲(chǔ)的是一個(gè)string的長(zhǎng)度缝左,但是對(duì)于list來說就會(huì)是這個(gè)list的元素格式亿遂。具體會(huì)以類型和編碼為主螟蒸。
在存儲(chǔ)過程中遵循如果是連續(xù)的內(nèi)存塊就使用string的存儲(chǔ)方式。這樣如果配置了壓縮可以使用到壓縮節(jié)約內(nèi)存崩掘。而且當(dāng)讀取的時(shí)候也可以根據(jù)C語(yǔ)言的內(nèi)存模型直接反向出原類型七嫌。
當(dāng)不是連續(xù)內(nèi)存塊的時(shí)候就度取出關(guān)鍵類型。當(dāng)做string來存儲(chǔ)(除了zset的那個(gè)source)苞慢。
文件存儲(chǔ)redisDb
現(xiàn)在我們知道了對(duì)于對(duì)象來說Redis的存儲(chǔ)方式诵原,對(duì)于我們的redisDb來說他就是一系列的key value,key是一個(gè)string value是個(gè)object所以存儲(chǔ)來說就會(huì)很簡(jiǎn)單
在存儲(chǔ)具體的值之前我們的db還需要一列額外的記錄挽放,比如當(dāng)前是哪個(gè)db啊绍赛。我們的這個(gè)key是不是有超時(shí)信息。所以可以看下這部分是怎么實(shí)現(xiàn)的
/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
//對(duì)于這些值來將會(huì)被當(dāng)做type來存儲(chǔ) 在load的時(shí)候讀取到這些類型將會(huì)被特殊處理
#define RDB_OPCODE_AUX 250 //設(shè)備信息 代表后面是兩個(gè)string作為鍵值對(duì)保存的設(shè)備的一個(gè)信息
#define RDB_OPCODE_RESIZEDB 251 //db的大小信息 代表后面會(huì)保存兩個(gè)長(zhǎng)度信息 一個(gè)保存數(shù)據(jù)的dict的大小 一個(gè)保存過期鍵的信息
#define RDB_OPCODE_EXPIRETIME_MS 252 //過期信息毫秒 代表后面將會(huì)讀取一個(gè)int64大小的數(shù)字
#define RDB_OPCODE_EXPIRETIME 253 //過期時(shí)間秒 后面會(huì)跟一個(gè)int32大小的時(shí)間
#define RDB_OPCODE_SELECTDB 254 //選擇數(shù)據(jù)庫(kù)
#define RDB_OPCODE_EOF 255 //數(shù)據(jù)庫(kù)保存結(jié)束標(biāo)記
/* Save an AUX field. */
//保存設(shè)備的一些信息 后面跟兩個(gè)string load的時(shí)候之間讀取
ssize_t rdbSaveAuxField(rio *rdb, void *key, size_t keylen, void *val, size_t vallen) {
ssize_t ret, len = 0;
if ((ret = rdbSaveType(rdb,RDB_OPCODE_AUX)) == -1) return -1;//標(biāo)記為設(shè)備信息
len += ret;
if ((ret = rdbSaveRawString(rdb,key,keylen)) == -1) return -1;//寫入key
len += ret;
if ((ret = rdbSaveRawString(rdb,val,vallen)) == -1) return -1;//寫入value
len += ret;
return len;
}
//保存db信息 后面之間跟兩個(gè)len信息
uint32_t db_size, expires_size;
db_size = (dictSize(db->dict) <= UINT32_MAX) ?
dictSize(db->dict) :
UINT32_MAX;
expires_size = (dictSize(db->expires) <= UINT32_MAX) ?
dictSize(db->expires) :
UINT32_MAX;
if (rdbSaveType(rdb,RDB_OPCODE_RESIZEDB) == -1) goto werr;//記錄數(shù)據(jù)庫(kù)大小信息
if (rdbSaveLen(rdb,db_size) == -1) goto werr;//dbsize
if (rdbSaveLen(rdb,expires_size) == -1) goto werr;//expires_size
//select
/* Write the SELECT DB opcode */
if (rdbSaveType(rdb,RDB_OPCODE_SELECTDB) == -1) goto werr;//進(jìn)入db的時(shí)候?qū)?huì)使用select
if (rdbSaveLen(rdb,j) == -1) goto werr;//用len直接來保存db的編號(hào)
//RDB_OPCODE_EXPIRETIME_MS
if (rdbSaveType(rdb,RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;
int64_t t64 = (int64_t) t;
return rdbWriteRaw(rdb,&t64,8);
//RDB_OPCODE_EXPIRETIME
//沒看到用 不過喃
if (rdbSaveType(rdb,RDB_OPCODE_EXPIRETIME) == -1) return -1;
int32_t t32 = (int32_t) t;
return rdbWriteRaw(rdb,&t32,4);
//RDB_OPCODE_EOF
rdbSaveType(rdb,RDB_OPCODE_EOF) ;//直接就是寫了個(gè)標(biāo)記
對(duì)于我們的沒有redisDb的保存項(xiàng)存儲(chǔ)的時(shí)候可以提取出三個(gè)東西key,value,expire辑畦。
來看下存儲(chǔ)著三個(gè)東西的流程
/* Save a key-value pair, with expire time, type, key, value.
* On error -1 is returned.
* On success if the key was actually saved 1 is returned, otherwise 0
* is returned (the key was already expired). */
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;//如果已經(jīng)過期了 肯定就不需要存儲(chǔ)了
if (rdbSaveType(rdb,RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;//首先存儲(chǔ)expire信息
if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;
}
/* Save type, key, value */
if (rdbSaveObjectType(rdb,val) == -1) return -1;//value type
if (rdbSaveStringObject(rdb,key) == -1) return -1; //key
if (rdbSaveObject(rdb,val) == -1) return -1;//value
return 1;
}
對(duì)于我們的流程來說首先就是判斷exipre是否存在是否過期等吗蚌。然后才是存儲(chǔ)我們的key value信息。
對(duì)于過期時(shí)間就是首先加上一個(gè)RDB_OPCODE_EXPIRETIME_MS后面之間跟上一個(gè)過期時(shí)間
對(duì)于key value 將首先存儲(chǔ)的是value的類型信息 然后將會(huì)保存string類型的key 最后會(huì)調(diào)用我們的object保存方法保存我們的value
對(duì)于存在exipre 就會(huì)是 RDB_OPCODE_EXPIRETIME_MS exipre valuetype key value 這種格式保存的纯出。
下面下整體的保存
/* Produces a dump of the database in RDB format sending it to the specified
* Redis I/O channel. On success C_OK is returned, otherwise C_ERR
* is returned and part of the output, or all the output, can be
* missing because of I/O errors.
*
* When the function returns C_ERR and if 'error' is not NULL, the
* integer pointed by 'error' is set to the value of errno just after the I/O
* error. */
int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
dictIterator *di = NULL;
dictEntry *de;
char magic[10];
int j;
long long now = mstime();//當(dāng)前的時(shí)間
uint64_t cksum;
size_t processed = 0;
if (server.rdb_checksum)
rdb->update_cksum = rioGenericUpdateChecksum; //校驗(yàn)和
snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);//首先寫入redis版本號(hào)信息 版本使用%04將會(huì)占用4個(gè)字節(jié)
if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;//redis 5個(gè)字節(jié)加上版本號(hào)四個(gè)字節(jié)為九個(gè)字節(jié)
if (rdbSaveInfoAuxFields(rdb,flags,rsi) == -1) goto werr; //寫入設(shè)備信息
for (j = 0; j < server.dbnum; j++) {//循環(huán)遍歷所有的db
redisDb *db = server.db+j;
dict *d = db->dict;
if (dictSize(d) == 0) continue;
di = dictGetSafeIterator(d);
if (!di) return C_ERR;
/* Write the SELECT DB opcode */
if (rdbSaveType(rdb,RDB_OPCODE_SELECTDB) == -1) goto werr;//進(jìn)入db的時(shí)候?qū)?huì)使用select
if (rdbSaveLen(rdb,j) == -1) goto werr;
/* Write the RESIZE DB opcode. We trim the size to UINT32_MAX, which
* is currently the largest type we are able to represent in RDB sizes.
* However this does not limit the actual size of the DB to load since
* these sizes are just hints to resize the hash tables. */
uint32_t db_size, expires_size;
db_size = (dictSize(db->dict) <= UINT32_MAX) ?
dictSize(db->dict) :
UINT32_MAX;
expires_size = (dictSize(db->expires) <= UINT32_MAX) ?
dictSize(db->expires) :
UINT32_MAX;
if (rdbSaveType(rdb,RDB_OPCODE_RESIZEDB) == -1) goto werr;//記錄數(shù)據(jù)庫(kù)大小信息
if (rdbSaveLen(rdb,db_size) == -1) goto werr;//dbsize
if (rdbSaveLen(rdb,expires_size) == -1) goto werr;//expires_size
/* Iterate this DB writing every entry */
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;
/* When this RDB is produced as part of an AOF rewrite, move
* accumulated diff from parent to child while rewriting in
* order to have a smaller final write. */
if (flags & RDB_SAVE_AOF_PREAMBLE &&
rdb->processed_bytes > processed+AOF_READ_DIFF_INTERVAL_BYTES)
{
processed = rdb->processed_bytes;
aofReadDiffFromParent();
}
}
dictReleaseIterator(di);
}
di = NULL; /* So that we don't release it again on error. */
/* If we are storing the replication information on disk, persist
* the script cache as well: on successful PSYNC after a restart, we need
* to be able to process any EVALSHA inside the replication backlog the
* master will send us. */
if (rsi && dictSize(server.lua_scripts)) {//lua
di = dictGetIterator(server.lua_scripts);
while((de = dictNext(di)) != NULL) {
robj *body = dictGetVal(de);
if (rdbSaveAuxField(rdb,"lua",3,body->ptr,sdslen(body->ptr)) == -1)
goto werr;
}
dictReleaseIterator(di);
}
/* EOF opcode */
if (rdbSaveType(rdb,RDB_OPCODE_EOF) == -1) goto werr;//保存eof
/* 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);
if (rioWrite(rdb,&cksum,8) == 0) goto werr;//保存校驗(yàn)和
return C_OK;
werr:
if (error) *error = errno;
if (di) dictReleaseIterator(di);
return C_ERR;
}
對(duì)于我們的保存的流程
1.保存魔數(shù)和version 將使用raw的方式直接寫入固定長(zhǎng)度
2.保存aux信息 aux信息有很多組都是固定的格式 保存aux標(biāo)記后后面跟上兩個(gè)string信息
3.開始保存一個(gè)db的信息蚯妇。首先使用RDB_OPCODE_SELECTDB來保存db的id,后面是直接放置的一個(gè)len的數(shù)字暂筝。然后使用RDB_OPCODE_RESIZEDB標(biāo)記來保存dbsize 和expires_size 箩言。
3具體保存每一個(gè)key value 信息。如果存在expire信息就是會(huì)RDB_OPCODE_EXPIRETIME_MS int64(time) valuetype key value 這種格式存儲(chǔ)焕襟。沒有expire的話就是直接valuetype key value 保存信息陨收。
4.保存eof信息這個(gè)就是直接保存一個(gè)標(biāo)記
5.最后保存一個(gè)8字節(jié)的校驗(yàn)和