String
String是Redis中最常见的数据存储类型:
其基本编码方式是RAW,基于简单动态字符串(SDS)实现,存储上限为512mb。如果存储的SDS长度小于44字节,则会采用EMBSTR编码,此时object head与SDS是一段连续空间。申请内存时只需要调用一次内存分配函数,效率更高。如果string类型的value是数字的话,那么redis会将其转化为Long类型进行存储,进一步节省空间
RAW编码的结构图:

可以看到RAW类型的结构是通过一个指针执行存储的SDS,这样会申请两次内存空间
EMBSTR编码结构图

可以看到 EMBSTR编码时,直接将SDS拼接到RedisObject后面,这样只要申请一次内存空间就行了,而且我们可以发现当长度为44个字节时,SDS部分的长度是1 + 1 + 1 + 44 + 1 = 48,加上前面的RedisObject部分的16,则正好是64字节,正好是2^6,因为内存申请都是申请2的整数次方,所以EMBSTR编码最大限制就是44字节
最后最后就是三种编码结构的对比:

其中前两种编码使⽤的是sds来存储,最后⼀种OBJ_ENCODING_INT编码直接把string存成了long型。 在对string进行incr, decr等操作的时候,如果它内部是OBJ_ENCODING_INT编码,那么可以直接行加减操作;如果它内部是OBJ_ENCODING_RAW或OBJ_ENCODING_EMBSTR编码,那么Redis会先试图把sds存储的字符串转成long型,如果能转成功,再进行加减操作。对⼀个内部表示成long型的string执行append, setbit, getrange这些命令,针对的仍然是string的值(即⼗进制表示的字符串),而不是针对内部表⽰的long型进⾏操作。
List
redis中的list结构可以从首尾插入元素,那么上篇文章提到的哪种数据结构可以满足呢?其实有三种数据结构都可以满足
-
LinkedList :普通链表,可以从双端访问,内存占用较高,内存碎片较多
-
ZipList :压缩列表,可以从双端访问,内存占用低,存储上限低
-
QuickList:LinkedList + ZipList,可以从双端访问,内存占用较低,包含多个ZipList,存储上限高
可以用这样的结构图来表示

值得一提的是插入部分的源码都是调用了一个函数

其中xx参数表示的是key是否存在,如果是true则必须在key存在时才进行插入,默认为false
Set
Set是Redis中的单列集合,满足下列特点:
-
不保证有序性
-
保证元素唯一
-
求交集、并集、差集
所以我们可以使用哈希结构来实现set的这些功能,我们可以看一下源码来验证一下

可以看到,源码中采用了两种结构,还有一个intset,为什么要用intset呢?因为,当存储的是数字的时候,使用intset更加节省空间,但是在存储的时候要做判断,是要要转换成哈希类型
set 的结构图如图所示

可以看到虽然使用的是哈希键值对存储,但是val都设置为null就可以达到只存储值的效果。
这里附上添加元素的源码

可以看到有两种情况需要转换编码格式:
1、intset中的元素超过了上限
2、当前添加的元素不是整数
ZSET
ZSet也就是SortedSet,其中每一个元素都需要指定一个score值和member值:
-
可以根据score值排序后
-
member必须唯一
-
可以根据member查询分数
可以满足这些条件的有两个数据结构:哈希表和跳表
哈希表:
可以确保member的唯一性
可以根据member查询分数
跳表:
可以根据score值排序
可以根据member查询分数
可以发现二者都只满足了一部分数据结构,那么把二者结合起来就会可以都满足了
所以看一下对应的源码就可以发现,源码中是维护了两种结构

对应的数据结构如图所示

当元素数量不多时,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值升序排列

有两种情况会将编码方式转化为跳表:
1、zset_max_ziplist_entries 设置为0时,表示禁用了ZipList方式
2、 初始化元素val大于zset_max_ziplist_value时
在添加元素时,也会对元素进行判断是否要转换数据结构和编码方式
使用ZipList存储的时候,采用的相邻存储的方式,即一对表示一个元素,再通过逻辑判断的方式来实现排序,注意这种方式只适用于元素个数较少的情况,当元素个数较少时,性能差别并不大,所以选择采用ZipList进行存储

Hash
刚说完zset,其实hash和zset十分相似,我们可以对比一下二者的异同
Hash结构与Redis中的Zset非常类似:
-
都是键值存储
-
都需求根据键获取值
-
键必须唯一
区别如下:
-
zset的键是member,值是score;hash的键和值都是任意值
-
zset要根据score排序;hash则无需排序
所以redis采取了和zset相似的处理,在数据量较小的时候采用了ZipList,数据量较大的时候采用的是dict结构
当Hash中数据项比较少的情况下,Hash底层才⽤压缩列表ziplist进⾏存储数据,随着数据的增加,底层的ziplist就可能会转成dict,具体配置如下:
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
下面是对应的添加hash元素的源码

其中hashTypeLookupWriteOrCreat主要是判断key是否存在,元素的大小是否超出了hash-max-ziplist-value的限制

循环中hashTypeSet主要是判断添加的数据数量是否超过hash-max-ziplist-entries的限制

对应的结构图如下

若转换成dict则编码方式encoding会改变为OBJ_ENCODING_HT