String
string类型使用的数据结构有三种:int,raw,embstr
当保存的是数字类型的时候,底层存的是int类型。
如图:
当保存的是一个字符串,并且字符串大于32字节的时候,底层存的是raw类型。
如图:
当保存的是一个字符串,并且字符串小于等于32字节的时候,底层存的是embstr类型。
embstr和raw的区别
embstr只需要分配一次内存空间,因为redis对于embstr类型的,会分配一个连续的内存空间,而对于raw类型,则会需要分配两次内存(redisObject和sdshdr分别申请)空间。
embstr格式的全部数据都在一串连续的空间中,所以使用这种格式的数据,更好的利用缓存带来的优势。
编码的转换
任何append操作,都会使得其他的类型转换成raw类型。
因为embstr格式,redis并没有给它有任何修改的api,所以本质上,embstr是只读的,所以append操作,会直接把其转成raw格式。
List
List类型使用的数据结构有两种:ziplist,linkedlist
当列表对象可以同时满足以下两个条件时,列表对象使用ziplist编码:
列表对象保存的所有字符串元素的长度都小于64字节;
列表对象保存的元素数量小于512个;
不能满足这两个条件的列表对象需要使用linkedlist编码。
以上两个条件的上限值是可以修改的,具体请看配置文件中关于list-max-ziplist-value选项和list-max-ziplist-entries选项的说明。
Hash
hash类型使用的数据结构有两种:ziplist和hashtable
使用ziplist的情况,ziplist里面存的数据如下所示:
每次hash新增key的时候,key和value按顺序push到list的末尾。
编码使用情况适用
当满足所有以下情况时,hash使用ziplist:
- hash保存的key和value的大小都小于64字节
- hash里面key的数量小于512个。
使用情况分析
案例1:hash使用的是ziplist(100个key)
在实际使用过程中,hash的数据结构有时候很重要。比如说有一个hash结构,存的key是uid,value是时间戳。
使用hmget这个指令,获取用户的信息。比如说传入的用户有一万个,hash里面存了100个,那么hmget需要遍历是10000*100次。如果是十万个用户,那这个遍历次数更多了。因为ziplist结构,每次查询,都可能遍历100次(大部分uid不存在)。
而如果使用的是hashtable的话,遍历次数就少了100(元素数量)次。
解决方案:如果key的数量较少使用的是ziplist,不使用hmget,而是一次性获取全部的值,在本地构建map去运算。
Set
set使用的数据结构有以下两种:intset和hashtable
首先介绍一下intset的结构。
typedef struct intset{
//编码方式
uint32_t encoding;
//集合包含的元素数量
uint32_t length;
//保存元素的数组
int8_t contents[];
}intset;
如上代码,可以看出,intset是使用contents数组来保存元素的,contents数组是有序的,判断数字是否在可以通过二分查找进行。
set结构上的hashtable跟普通的hashtable的区别就是其value都是null。这跟java中的Set实现很像,在java中,HashSet的底层实现就是HashMap,只不过,value都是无意义的Object对象。
编码转换
当set同时满足以下条件的时候,set使用intset结构:
- set里面存储的元素都是int类型。
- set最大数量小于512个。
ZSET
zset使用的数据结构有以下两种:ziplist和skiplist
当使用skiplist结构是,zset实际上是使用dict和skiplist一起实现的。
因为其需要两者的特性,所以需要两者结合在一起使用。比如说,zrange 需要使用跳表的特性,才能快速range,而判断这个成员的分数等,则需要使用dict结构快速定位成员。
typedef struct zset
{
zskiplist *zsl;
dict *dict;
} zset;
编码转换
当有序集合对象可以同时满足以下两个条件时,对象使用ziplist编码:
- zset保存的元素数量小于128个;
- zset保存的所有元素成员的长度都小于64字节;