0. 写在前面
参考文章
对象的类型和编码
Redis中的每个对象都是由 redisObject 结构来表示:
typedef struct redisObject{
//类型
unsigned type:4;
//编码
unsigned encoding:4;
//指向底层数据结构的指针
void *ptr;
//引用计数
int refcount;
//记录最后一次被程序访问的时间
unsigned lru:22;
}robj
127.0.0.1:6379> set k1 ljj
OK
127.0.0.1:6379> type k1
string
127.0.0.1:6379> object encoding k1
"embstr"
127.0.0.1:6379>
type属性
对象的type属性记录了对象的类型,这个类型就是前面讲的五大数据类型:
encoding 属性和 *prt 指针
对象的 prt 指针指向对象底层的数据结构,而数据结构由 encoding 属性来决定。
1. 字符串对象
1.1 编码
127.0.0.1:6379> set k1 1
OK
127.0.0.1:6379> object encoding k1
"int"
127.0.0.1:6379> set k2 aaaaaaabbbbbb
OK
127.0.0.1:6379> object encoding k2
"embstr"
127.0.0.1:6379> set k3 aaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccc
OK
127.0.0.1:6379> object encoding k3
"raw"
127.0.0.1:6379>
字符串对象的编码可以是int,raw或者embstr。
1、int 编码:保存的是可以用 long 类型表示的整数值。
2、raw 编码:保存长度大于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)。
3、embstr 编码:保存长度小于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)。
embstr编码是专门用来保存短字符串的一种优化编码;相比于raw,embstr的redisObject和sds内存是连续的,好处是创建时少分配一次空间,删除时少释放一次空间,坏处是embstr是只读的,数据如果变更,需要转化为raw。
1.2 编码的转换
当 int 编码保存的值不再是整数,或大小超过了long的范围时,自动转化为raw;
对embstr对象进行修改时,都会先转化为raw再进行修改,无论是否达到了44个字节;
2. 列表对象
list 列表,它是简单的字符串列表,按照插入顺序排序,你可以添加一个元素到列表的头部(左边)或者尾部(右边),它的底层实际上是个链表结构。
2.1 编码
列表对象的编码可以是 ziplist(压缩列表) 和 linkedlist(双端链表)
rpush numbers 1 "three" 5
2.2 编码转换
当同时满足下面两个条件时,使用ziplist(压缩列表)编码:
1、列表保存元素个数小于512个
2、每个元素长度小于64字节
不能满足这两个条件的时候使用 linkedlist 编码。
2.3 优化后的编码:quicklist (redis3.2版本)
127.0.0.1:6379> lpush my_list 1 2 3 4 5 6
(integer) 6
127.0.0.1:6379> object encoding my_list
"quicklist"
quickList 是 zipList 和 linkedList 的混合体,它将 linkedList 按段切分,每一段使用 zipList 来紧凑存储,多个 zipList 之间使用双向指针串接起来。
3. 哈希对象
3.1 编码
哈希对象的编码可以是 ziplist 或者 hashtable。
127.0.0.1:6379> hmset profile name ljj age 25 career Programmer
OK
127.0.0.1:6379> object encoding user
"ziplist"
127.0.0.1:6379> hset user desc aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
(integer) 1
127.0.0.1:6379> object encoding user
"hashtable"
127.0.0.1:6379>
3.2 编码转换
和上面列表对象使用 ziplist 编码一样,当同时满足下面两个条件时,使用ziplist(压缩列表)编码:
1、列表保存元素个数小于512个
2、每个元素长度小于64字节
不能满足这两个条件的时候使用 hashtable 编码。第一个条件可以通过配置文件中的 set-max-intset-entries 进行修改。
4. 集合对象
集合对象 set 是 string 类型的无序集合。注意集合和列表的区别:集合中的元素是无序的,因此不能通过索引来操作元素;集合中的元素不能有重复。
4.1 编码
127.0.0.1:6379> sadd num 123
(integer) 1
127.0.0.1:6379> object encoding num
"intset"
127.0.0.1:6379> sadd fruits "apple" "banana" "cherry"
(integer) 3
127.0.0.1:6379> object encoding fruits
"hashtable"
127.0.0.1:6379>
集合对象的编码可以是 intset 或者 hashtable。
4.2 编码转化
当集合同时满足以下两个条件时,使用 intset 编码:
1、集合对象中所有元素都是整数
2、集合对象所有元素数量不超过512
不能满足这两个条件的就使用 hashtable 编码。第二个条件可以通过配置文件的 set-max-intset-entries 进行配置。
5. 有序集合对象
和上面的集合对象相比,有序集合对象是有序的。与列表使用索引下标作为排序依据不同,有序集合为每个元素设置一个分数(score)作为排序依据。
5.1 编码
127.0.0.1:6379> ZADD price 8.5 apple 5.0 banana 6.0 cherry
(integer) 3
127.0.0.1:6379> object encoding price
"ziplist"
127.0.0.1:6379> zadd price 10 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccccccccccccccccccccccccccccccc
(integer) 1
127.0.0.1:6379> object encoding price
"skiplist"
skiplist 编码的有序集合对象使用 zet 结构作为底层实现,一个 zset 结构同时包含一个字典和一个跳跃表:
typedef struct zset{
//跳跃表
zskiplist *zsl;
//字典
dict *dice;
} zset;
字典的键保存元素的值,字典的值则保存元素的分值;跳跃表节点的 object属性保存元素的成员,跳跃表节点的 score 属性保存元素的分值。
这两种数据结构会通过指针来共享相同元素的成员和分值,所以不会产生重复成员和分值,造成内存的浪费。
说明:其实有序集合单独使用字典或跳跃表其中一种数据结构都可以实现,但是这里使用两种数据结构组合起来,原因是假如我们单独使用 字典,虽然能以 O(1) 的时间复杂度查找成员的分值,但是因为字典是以无序的方式来保存集合元素,所以每次进行范围操作的时候都要进行排序;假如我们单独使用跳跃表来实现,虽然能执行范围操作,但是查找操作有 O(1)的复杂度变为了O(logN)。因此Redis使用了两种数据结构来共同实现有序集合。
5.2 编码转换
当有序集合对象同时满足以下两个条件时,对象使用 ziplist 编码:
1、保存的元素数量小于128;
2、保存的所有元素长度都小于64字节。
不能满足上面两个条件的使用 skiplist 编码。以上两个条件也可以通过Redis配置文件zset-max-ziplist-entries 选项和 zset-max-ziplist-value 进行修改。