参考资料
极客时间Redis(亚风)
? 基本编码?式是RAW,基于简单动态字符串(SDS)实现,存储上限为512mb。
? 如果存储的SDS?度?于44字节,则会采?EMBSTR编码,此时object head与SDS是?段连续空间。申请内存时只需要调??次内存分配函数,效率更?。
? 如果存储的字符串是整数值,并且??在LONG_MAX范围内,则会采?INT编码:直接将数据保存在RedisObject的ptr指针位置(刚好8字节),不再需要SDS了。
对应第一种情况:
对应第二种情况:
对应第三种情况:
Redis采?QuickList实现List。
Set是Redis中的集合,不?定确保元素有序,可以满?元素唯?、查询效率要求极?。
为了查询效率和唯?性,set采?HT编码(Dict)。Dict中的key?来存储元素,value统?为null.
当存储的所有数据都是整数,并且元素数量不超过set-max-intset-entries时,Set会采?lntSet编码,以节省内存。
第一中情况都是int类型:IntSet 实现
第二种:使用dict实现
上面过程对应的源码:
int setTypeAdd (robj *subject, sds value) {
Long Long llval;
if (subject->encoding==OBJ_ENCODING_HT) {
//已经是HT编码,直接添加元素
dict *ht = subject->ptr;
dictEntry *de = dictAddRaw(ht,value,NULL);
if (de) {
dictSetKey(ht, de, sdsdup (value));
dictSetVal(ht,de, NULL);
return 1 ; }
}
else if (subject->encoding == OBJ_ENCODING_INTSET){
// ?前是INTSET
// 判断value是否是整数
if (isSdsRepresentableAsLongLong(value,&llval) == C_OK) {
uint8_t success = 0;
// 是整数,直接添加元素到set
subject->ptr=intsetAdd (subject->ptr, llval, &success);
if (success){
/* 当intset元素数量超出set_max_intset_entries,则转为HT.*/
size_t max_entries = server.set_max_intset_entries;
if (max_entries >= 1<<30) max_entries=1<<30;
if (intsetLen(subject->ptr) > max_entries)
setTypeConvert(subject,OBJ_ENCODING_HT);
return 1;
}
) else {
// 不是整数,直接转为HT
setTypeConvert(subject,OBJ_ENCODING_HT);
serverAssert(dictAdd(subject->ptr,sdsdup (value),NULL)== DICT_OK);
return 1;}
} }
底层是用三种数据结构实现的(HT + SkipList + ZPlist)
typedef struct zset {
// Dict指针
dict *dict;
// SkipList指针
zskiplist *zsl;
} zset;
当元素数量不多时,HT和SkipList的优势不明显,?且更耗内存。因此zset还会采?ZipList结构来节省内存,不过需要同时满?两个条件:
① 元素数量?于zset-max-ziplist-entries,默认值128
② 每个元素都?于zset-max-ziplist-value字节,默认值64
源码如下:
// zadd添加元素时,先根据key找到zset,不存在则创建新的zset
zobj = LookupKeywrite(c->db, key);
if (checkType (c, zobj,OBJ_ZSET)) goto cleanup;
//判断是否存在
if (zobj == NULL) {// zset不存在
if (server.zset_max_ziplist_entries == 0
server.zset_max_ziplist_value < sdslen(c->argv [scoreidx+1]->ptr))
{
// zset_max_ziplist_entries设置为0就是禁?了ZipList,
//或者value??超过了zset_max_ziplist_value,采?HT + SkipList
Zobj = createzsetobject();
} else {
// 否则,采? ZipList
zobj = createzsetziplistobject();
}
dbAdd(c->db, key, zobj);
}
zsetAdd(zobj,score, ele, flags, &retflags, &newscore);
Ziplist本身没有排序功能,?且没有键值对的概念,因此需要有zset通过编码实现:
? Ziplist是连续内存,因此score和element是紧挨在?起的两个entry,element在前,score在后
? score越?越接近队?,score越?越接近队尾,按照score值升序排列
Hash底层采?的编码与Zset也基本?致,只需要把排序有关的SkipList去掉即可:
? Hash结构默认采?ZipList编码,?以节省内存。Ziplist中相邻的两个entry 分别保存field和value
? 当数据量较?时,Hash结构会转为HT编码,也就是Dict,触发条件有两个:
① ZipList中的元素数量超过了hash-max-ziplist-entries(默认512)
② ZipList中的任意entry??超过了hash-max-ziplist-value(默认64字节)
// 插入命令
void hsetCommand(client *c) {
int i, created = 0;
robj *o;
//判断hash的key是否存在,不存在则创建?个新的,默认采?ZipList编码
if ((o = hashTypeLookupwriteOrCreate(c, c->argv[1]))==NULL) return;
// 判断是否需要把ZipList转为Dict
hashTypeTryConversion(o,c->argv,2,c->argc-1);
// 循环遍历每?对field和value,井执?hset命令
for (i = 2;i<c-argc; i += 2)
created += !hashTypeSet(o,c->argv[I]->ptr,c->argv[i+1]->ptr, HASH_SET_COPY);
}
robj *hashTypeLookupwriteOrCreate(client *c, robj *key)
// 查找key
robj *o = LookupKeywrite(c->db, key);
if (checkType(c,o, OBJ_HASH)) return NULL;
// 不存在,则创建新的
if (o == NULL) {
o = createHashobject();
dbAdd (c->db, key,o);
}
return o;
}
robj *createHashobject (void) {
// 默认采?zipList编码,申请ZipList内存空间
unsigned char *z1 = ziplistNew();
robj *o = createobject(OBJ_HASH, z1);
//设置编码
o->encoding = OBJ_ ENCODING_ZIPLIST;
return 0;
}
// ziplist 在什么时候转换为HT
void hashTypeTryConversion(robj *o,robj **argv, int start, int end)
int i;size_t sum = 0;
// 本来就不是ZipList编码,什么都不?做了
if (o->encoding != OBJ_ENCODING_ZIPLIST) return;
//依次遍历命令中的field、 value参数
for (i = s. start; i < c. end;j++)
if (!sdsEncodedobject (argv[i]))
continue;
size_t len = sdsLen (argv[i]->ptr);
// 如果field或value超过hash_max_ziplist_value,则转为HT
if (len > server.hash_max_ziplist_value) {
hashTypeConvert(o, OBJ_ENCODING_HT);
return;
}
sum += len;
}
// ziplist??超过1G,也转为HT
if (!ziplistSafeToAdd(o- >ptr, sum))
hashTypeConvert(o, OBJ_ENCODING_HT);
}
// 真正插入元素的逻辑
int hashTypeSet(robj *o,sds field, sds value, int flags){
int update = 0;
// 判断是否为ZipList编码
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
unsigned char *zl, *fptr, *vptr;
zl = o->ptr;
// 查询head指针
fptr = ziplistIndex(zl, ZIPLIST_HEAD);
if (fptr != NULL) 〔 // head不为空,说明ZipList不为空,开始查找key
fptr = ziplistFind(zl, fptr, (unsigned char*) field, sdslen(value));
if (fptr != NULL){//判断是否存在,如果已经存在则更新
update=1;
ziplistReplace(zl, vptr, (unsigned char*) value,sdslen (value));
// 不存在,则直接push
if(!update){ // 依次push新的field和value到zipList的尾部
zl = ziplistPush(zl, (unsigned char*) field, sdslen(field),ZIPLIST_TAIL);
zl = ziplistPush(zl, (unsigned char*)value, sdslen (value),ZIPLIST_TAIL);}
o->ptr = zl;
/* 插?了新元素,检查list?度是否超出,超出则转为HT */
if (hashTypeLength(o)>server.hash_max_ziplist_entries)
hashTypeConvert(o, OBJ_ENCODING_HT);
} else if (o->encoding == OBJ_ENCODING_HT) {
// HT编码,直接插?或覆盖
}
return update;
}