1 string
基本编码方式,基于简单动态字符串(SDS)实现,存储上线为512mb.
如果存储的SDS长度小于44字节,则会采用EMBSTR编码,此时object head与SDS是一段连续空间。申请内存时只需要调用一次内存分配函数,效率更高。
如果存储的字符串是整数值,并且大小在LONG MAX范围内,则会采用INT编码:直接将数据保存在Redisobjiect的ptr指针位置(刚好8字节),不再需要SDS了。
结论
RAW会有两次内存分配,效率较低
如果sds长大衣小于44字节,则会使用EMBSTR编码方式,只分布一次内存
INT编码方式,则sds中存储的是正数值,且数值范围小于Long.Max
2 List
Redis的List类型可以从首尾操作。
根据List的操作可以使用如下的编码方式。
LinkedList:普通链表,可以从双端访问,内存占用较高,内存碎片较多
ZipList: 压缩列表,可以从双端访问,内存占用低,存储上限低
QuickList : LinkedList + zipList,可以从双端访问,内存占用较低,包含多个ZipList,存储上限高
/* LPUSH <key> <element> [<element> ...] */
void lpushCommand(client *c) {
pushGenericCommand(c,LIST_HEAD,0);
}
/* RPUSH <key> <element> [<element> ...] */
void rpushCommand(client *c) {
pushGenericCommand(c,LIST_TAIL,0);
}
/* Implements LPUSH/RPUSH/LPUSHX/RPUSHX.
* 'xx': push if key exists. */
// where 表示从头插还是尾插
void pushGenericCommand(client *c, int where, int xx) {
int j;
// 将命令存放到argv中 LPUSH key v1 v2
for (j = 2; j < c->argc; j++) {
if (sdslen(c->argv[j]->ptr) > LIST_MAX_ITEM_SIZE) {
addReplyError(c, "Element too large");
return;
}
}
// 找到key对象的list
robj *lobj = lookupKeyWrite(c->db, c->argv[1]);
// 类型校验
if (checkType(c,lobj,OBJ_LIST)) return;
// 检查是否为空,list不存在
if (!lobj) {
if (xx) {
addReply(c, shared.czero);
return;
}
// 创建quickList
lobj = createQuicklistObject();
// ziplist的限制
quicklistSetOptions(lobj->ptr, server.list_max_ziplist_size,
server.list_compress_depth);
dbAdd(c->db,c->argv[1],lobj);
}
for (j = 2; j < c->argc; j++) {
listTypePush(lobj,c->argv[j],where);
server.dirty++;
}
addReplyLongLong(c, listTypeLength(lobj));
char *event = (where == LIST_HEAD) ? "lpush" : "rpush";
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id);
}
3 Set
Set是Redis中的单列集合,满足下列特点
不保证有序
元素唯一(可以判断元素是否存在)
求交集、并集和差集
使用IntSet 或者 hash 结构。为了查询效率和唯一性,set采用HT编码 (Dict)。Dict中的key用来存储元素,value统一为null。
当存储的 所有数据都是整数 ,并且元素数量不超过set-max-intset-entries时,Set会采用IntSet编码,以节省内存。
4 ZSet
ZSet也就是SortedSet,其中每一个元素都需要指定一个score值和member值
可以根据score值排序后
member必须唯一
可以根据member查询分数
使用 sikpList 或者 (Dict) 结构
skipList:可以排序,并且可以同时存储score和ele值 (member)。无法根据member找score
HT (Dict):可以键值存储,并且可以根据key找value。无法排序
缺点:数据冗余,内存占用过高。
当元素数量不多时,HT和SkipList的优势不明显,而且更耗内存。因此zset还会采用ZipList结构来节省内存,不过需要同时满足两个条件:
元素数量小于zset max ziplist entries,默认值128
每个元素都小于zset max ziplist value字节,默认值64
插入元素时
ziplist本身没有排序功能,而且没有键值对的概念,因此需要有zset通过编码实现:
ZipList是连续内存,因此score和element是紧挨在一起的两个entry,element在前,score在后
score越小越接近队首,score越大越接近队尾,按照score值升序排列。
5 Hash
Hash结构与Redis中的Zset非常类似
都是键值存储
都需求根据键获取值
键必须唯一
Hash结构默认采用ZipList编码,用以节省内存。ZipList中相邻的两个entry 分别保存field和value。
当数据量较大时Hash结构会转为HT编码,也就是Dict,触发条件有两个:
ZipList中的元素数量超过了hash-max-ziplist-entries(默认512)
ZipList中的任意entry大小超过了hash-max-ziplist-value(默认64字节)
在不满足上述要求时会转换为HT
总结
String 底层采用RAW、 EMBSTR 和INT类型的编码。当SDS字节数不超过44字节时采用 EMBSTR,当为整数值时采用INT类型编码
List 底层采用quickList
Set 底层采用 使用IntSet 或者 hashtable 结构,当值都为整数时采用IntSet,一般采用hash,其value为null.
Zset 底层采用 SkipList +hashtable 或者 ziplist 结构
hash 底层采用zipList 和 hashtable 。
采用ziplist的原因是在小数据量情况下,效率高而且内存占用少。