参考资料
极客时间Redis(亚风)
Redis对象
String
• 基本编码⽅式是RAW,基于简单动态字符串(SDS)实现,存储上限为512mb。
• 如果存储的SDS⻓度⼩于44字节,则会采⽤EMBSTR编码,此时object head与SDS是⼀段连续空间。申请内存时只需要调⽤⼀次内存分配函数,效率更⾼。
• 如果存储的字符串是整数值,并且⼤⼩在LONG_MAX范围内,则会采⽤INT编码:直接将数据保存在RedisObject的ptr指针位置(刚好8字节),不再需要SDS了。
对应第一种情况:
对应第二种情况:
对应第三种情况:
List
Redis采⽤QuickList实现List。
Set
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;// 是整数,直接添加元素到setsubject->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 {// 不是整数,直接转为HTsetTypeConvert(subject,OBJ_ENCODING_HT);serverAssert(dictAdd(subject->ptr,sdsdup (value),NULL)== DICT_OK);return 1;}
} }
ZSet
底层是用三种数据结构实现的(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
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转为DicthashTypeTryConversion(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)// 查找keyrobj *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,则转为HTif (len > server.hash_max_ziplist_value) {hashTypeConvert(o, OBJ_ENCODING_HT);return;}sum += len;}// ziplist⼤⼩超过1G,也转为HTif (!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不为空,开始查找keyfptr = ziplistFind(zl, fptr, (unsigned char*) field, sdslen(value));if (fptr != NULL){//判断是否存在,如果已经存在则更新update=1;ziplistReplace(zl, vptr, (unsigned char*) value,sdslen (value));// 不存在,则直接pushif(!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;
}