系列
redis數(shù)據(jù)淘汰原理
redis過期數(shù)據(jù)刪除策略
redis server事件模型
redis cluster mget 引發(fā)的討論
redis 3.x windows 集群搭建
redis 命令執(zhí)行過程
redis string底層數(shù)據(jù)結(jié)構(gòu)
redis list底層數(shù)據(jù)結(jié)構(gòu)
redis hash底層數(shù)據(jù)結(jié)構(gòu)
redis set底層數(shù)據(jù)結(jié)構(gòu)
redis zset底層數(shù)據(jù)結(jié)構(gòu)
redis 客戶端管理
redis 主從同步-slave端
redis 主從同步-master端
redis 主從超時(shí)檢測(cè)
redis aof持久化
redis rdb持久化
redis 數(shù)據(jù)恢復(fù)過程
redis TTL實(shí)現(xiàn)原理
redis cluster集群建立
redis cluster集群選主
set底層存儲(chǔ)
?redis的集合對(duì)象set的底層存儲(chǔ)結(jié)構(gòu)特別神奇,我估計(jì)一般人想象不到漆羔,底層使用了intset和hashtable兩種數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)的捏浊,intset我們可以理解為數(shù)組嫌套,hashtable就是普通的哈希表(key為set的值,value為null)面哥。是不是覺得用hashtable存儲(chǔ)set是一件很神奇的事情贝咙。
?set的底層存儲(chǔ)intset和hashtable是存在編碼轉(zhuǎn)換的鸳慈,使用intset存儲(chǔ)必須滿足下面兩個(gè)條件,否則使用hashtable架馋,條件如下:
- 結(jié)合對(duì)象保存的所有元素都是整數(shù)值
- 集合對(duì)象保存的元素?cái)?shù)量不超過512個(gè)
?hashtable的數(shù)據(jù)結(jié)構(gòu)應(yīng)該在前面的hash的章節(jié)已經(jīng)介紹過了狞山,所以這里著重講一下intset這個(gè)新的數(shù)據(jù)結(jié)構(gòu)好了。
intset的數(shù)據(jù)結(jié)構(gòu)
?intset內(nèi)部其實(shí)是一個(gè)數(shù)組(int8_t coentents[]數(shù)組)叉寂,而且存儲(chǔ)數(shù)據(jù)的時(shí)候是有序的萍启,因?yàn)樵诓檎覕?shù)據(jù)的時(shí)候是通過二分查找來實(shí)現(xiàn)的。
typedef struct intset {
// 編碼方式
uint32_t encoding;
// 集合包含的元素?cái)?shù)量
uint32_t length;
// 保存元素的數(shù)組
int8_t contents[];
} intset;
redis set存儲(chǔ)過程
?以set的sadd命令為例子屏鳍,整個(gè)添加過程如下:
- 檢查set是否存在不存在則創(chuàng)建一個(gè)set結(jié)合勘纯。
- 根據(jù)傳入的set集合一個(gè)個(gè)進(jìn)行添加,添加的時(shí)候需要進(jìn)行內(nèi)存壓縮钓瞭。
- setTypeAdd執(zhí)行set添加過程中會(huì)判斷是否進(jìn)行編碼轉(zhuǎn)換驳遵。
void saddCommand(redisClient *c) {
robj *set;
int j, added = 0;
// 取出集合對(duì)象
set = lookupKeyWrite(c->db,c->argv[1]);
// 對(duì)象不存在,創(chuàng)建一個(gè)新的山涡,并將它關(guān)聯(lián)到數(shù)據(jù)庫(kù)
if (set == NULL) {
set = setTypeCreate(c->argv[2]);
dbAdd(c->db,c->argv[1],set);
// 對(duì)象存在堤结,檢查類型
} else {
if (set->type != REDIS_SET) {
addReply(c,shared.wrongtypeerr);
return;
}
}
// 將所有輸入元素添加到集合中
for (j = 2; j < c->argc; j++) {
c->argv[j] = tryObjectEncoding(c->argv[j]);
// 只有元素未存在于集合時(shí),才算一次成功添加
if (setTypeAdd(set,c->argv[j])) added++;
}
// 如果有至少一個(gè)元素被成功添加鸭丛,那么執(zhí)行以下程序
if (added) {
// 發(fā)送鍵修改信號(hào)
signalModifiedKey(c->db,c->argv[1]);
// 發(fā)送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sadd",c->argv[1],c->db->id);
}
// 將數(shù)據(jù)庫(kù)設(shè)為臟
server.dirty += added;
// 返回添加元素的數(shù)量
addReplyLongLong(c,added);
}
?稍微深入分析一下set的單個(gè)元素的添加過程竞穷,首先如果已經(jīng)是hashtable的編碼,那么我們就走正常的hashtable的元素添加鳞溉,如果原來是intset的情況瘾带,那么我們就需要進(jìn)行如下判斷:
- 如果能夠轉(zhuǎn)成int的對(duì)象(isObjectRepresentableAsLongLong),那么就用intset保存穿挨。
- 如果用intset保存的時(shí)候月弛,如果長(zhǎng)度超過512(REDIS_SET_MAX_INTSET_ENTRIES)就轉(zhuǎn)為hashtable編碼。
- 其他情況統(tǒng)一用hashtable進(jìn)行存儲(chǔ)科盛。
/*
* 多態(tài) add 操作
*
* 添加成功返回 1 帽衙,如果元素已經(jīng)存在,返回 0 贞绵。
*/
int setTypeAdd(robj *subject, robj *value) {
long long llval;
// 字典
if (subject->encoding == REDIS_ENCODING_HT) {
// 將 value 作為鍵厉萝, NULL 作為值,將元素添加到字典中
if (dictAdd(subject->ptr,value,NULL) == DICT_OK) {
incrRefCount(value);
return 1;
}
// intset
} else if (subject->encoding == REDIS_ENCODING_INTSET) {
// 如果對(duì)象的值可以編碼為整數(shù)的話,那么將對(duì)象的值添加到 intset 中
if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
uint8_t success = 0;
subject->ptr = intsetAdd(subject->ptr,llval,&success);
if (success) {
// 添加成功
// 檢查集合在添加新元素之后是否需要轉(zhuǎn)換為字典
// #define REDIS_SET_MAX_INTSET_ENTRIES 512
if (intsetLen(subject->ptr) > server.set_max_intset_entries)
setTypeConvert(subject,REDIS_ENCODING_HT);
return 1;
}
// 如果對(duì)象的值不能編碼為整數(shù)谴垫,那么將集合從 intset 編碼轉(zhuǎn)換為 HT 編碼
// 然后再執(zhí)行添加操作
} else {
setTypeConvert(subject,REDIS_ENCODING_HT);
redisAssertWithInfo(NULL,value,dictAdd(subject->ptr,value,NULL) == DICT_OK);
incrRefCount(value);
return 1;
}
// 未知編碼
} else {
redisPanic("Unknown set encoding");
}
// 添加失敗章母,元素已經(jīng)存在
return 0;
}