Redis的五种数据结构的内部编码
type命令实际返回的就是当前键的数据结构类型,它们分别是string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合) 等,这些只是Redis对外的数据结构。
实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现,这样Redis会在适合的场景选择合适的内部编码。
可以看到每种数据结构都有两种以上的内部编码实现,例如string数据类型就包含了raw、int、embstr三种内部编码。
同时,有些内部编码可以作为多种外部数据结构的内部实现,例如ziplist就是hash、list和zset共有的内部编码。
我们可以通过object encoding命令查询内部编码:
internal-156.sdjnsoft.com:16379> set hello redis
OK
internal-156.sdjnsoft.com:16379> type hello
string
internal-156.sdjnsoft.com:16379> object encoding hello
"embstr"
internal-156.sdjnsoft.com:16379> hmset user:100001 name zhang3 age 28
OK
internal-156.sdjnsoft.com:16379> type user:100001
hash
internal-156.sdjnsoft.com:16379> object encoding user:100001
"ziplist"
internal-156.sdjnsoft.com:16379>
可以看到键hello对应值得内部编码是empstr,键user:100001的内部编码是ziplist。
Redis这样设计有两个好处:
- 可以改进内部编码,而对外的数据结构和命令没有影响,这样一旦开发者开发出优秀的内部编码,无需改动外部数据结构和命令。
- 多种内部编码实现可以在不同场景下发挥各自的优势,例如ziplist比较省内存,但是在列表元素比较多的情况下,性能会有所下降,这时候Redis会根据配置将选项将列表类型的内部实现转换为linkedList 。
五种数据结构的内部编码方式
1. 字符串的内部编码
字符串类型的内部编码有3种:
- int: 8个字节的长整型。
- embstr: 小于等于39个字节的字符串。
- raw: 大于39个字节的字符串。
Redis会根据当前值得类型和长度决定内部编码实现。
(1)整数类型示例如下:
internal-156.sdjnsoft.com:16379> set str:int 123456789
OK
internal-156.sdjnsoft.com:16379> object encoding str:int
"int"
(2)短字符串示例如下:
internal-156.sdjnsoft.com:16379> set str:embstr "hello world"
OK
internal-156.sdjnsoft.com:16379> object encoding str:embstr
"embstr"
(3)长字符串示例如下:
internal-156.sdjnsoft.com:16379> set str:raw "概要设计有多种方法。在早期有模块化方法、功能分解方法;在60年代后期提出了面向数据流和面向数据结构的设计方法;近年来又提出面向对象的设计方法等。"
OK
internal-156.sdjnsoft.com:16379> object encoding str:raw
"raw"
2. 哈希的内部编码
哈希类型的内部编码有两种:
- ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个),同时所有值都小于hash-max-ziplist-value配置(默认64字节)时,所以在节省内存方面比hashtable更加优秀。
- hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为hash的内部实现,因此此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1).
O(n²)表示当n很大的时候,复杂度约等于Cn²,C是某个常数,简单说就是当n足够大的时候,n的线性增长,复杂度将沿平方增长。
O(n)也是差不多的意思,也就是说n很大的时候复杂度约等于Cn,C是某个常数。
O(1)就是说n很大的时候,复杂度基本就不增长了,基本就是个常量C。
下面展示哈希类型的内部编码,及相应的变化。
(1) 当field个数比较少且没有大的value时,内部编码为ziplist;
internal-156.sdjnsoft.com:16379> hmset user:100002 name zhang3 age 19 sex 男
OK
internal-156.sdjnsoft.com:16379> object encoding user:100002
"ziplist"
(2) 当有value大于64个自己,内部编码会有ziplist变为hashtable;
internal-156.sdjnsoft.com:16379> hmset user:100003 info "概要设计有多种方法。在早期有模块化方法、功能分解方法;在60年代后期提出了面向数据流和面向数据结构的设计方法;近年来又提出面向对象的设计方法等。"
OK
internal-156.sdjnsoft.com:16379> object encoding user:100003
"hashtable"
(3)当field个数超过512,内部编码也会变为hashtable;
// 略.
注意:当一个哈希的编码由ziplist变为hashtable的时候,即使在替换掉所有值,它一直都会是hashtable类型。
3. 列表的内部编码
列表类型的内部编码有两种:
- ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个),同时所有值都小于hash-max-ziplist-value配置(默认64字节),Redis会使用ziplist作为哈希的内部实现。
- linkedlist(链表):当列表类型午饭满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。
(1)当元素个数较少且没有大元素时,内部编码为ziplist:
internal-156.sdjnsoft.com:16379> rpush list:2 a b c
(integer) 3
internal-156.sdjnsoft.com:16379> object encoding list:2
"ziplist"
(2)当元素个数超过512个,内部编码变为linkedlist:
internal-156.sdjnsoft.com:16379>lpush setkey 1 2 3 ... 513
OK
internal-156.sdjnsoft.com:16379> object encoding listkey
"linkedlist"
(3)当某个元素超过64个字节,内部编码也会变为linkedlist:
internal-156.sdjnsoft.com:16379> rpush list:1 a b "我不再说话,不再思索,但无尽的爱从灵魂中升起,我将远行,走得很远,如同一个吉普塞人,穿过大自然——幸福得如有一位女子同行。"
(integer) 6
internal-156.sdjnsoft.com:16379> object encoding list:1
"linkedlist"
内部编码规则只能升级。不能自动变回ziplist
3.2
- 3.2之后加入的新的数据结构quicklist,用作redis对外提供的五种数据类型—list的底层实现。
- 考虑到链表的附加空间相对太高,prev 和 next 指针就要占去 16 个字节 (64bit 系统的指针是 8 个字节),另外每个节点的内存都是单独分配,会加剧内存的碎片化,影响内存管理效率。 后续版本对列表数据结构进行了改造,使用 quicklist 代替了 ziplist 和 linkedlist.
- quickList 是 zipList 和 linkedList 的混合体,它将 linkedList 按段切分,每一段使用 zipList 来紧凑存储,多个 zipList 之间使用双向指针串接起来。
- quicklist 默认的压缩深度是 0,也就是不压缩。压缩的实际深度由配置参数list-compress-depth决定。为了支持快速的 push/pop 操作,quicklist 的首尾两个 ziplist不压缩,此时深度就是 1。如果深度为 2,就表示 quicklist 的首尾第一个 ziplist 以及首尾第二个 ziplist 都不压缩。
- quicklist 内部默认单个 ziplist 长度为 8k 字节,超出了这个字节数,就会新起一个 ziplist。 ziplist 的长度由配置参数 list-max-ziplist-size 决定。
4. 集合的内部编码
集合类型的内部编码有两种:
- intset(整数集合): 当集合中的元素都是整数且元素个数小于set-max-intset-entries(默认512个)时,Redis会选用intset来作为集合内部实现,从而减少内存使用。
- hashtable(哈希表):当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。
下面来举例说明:
(1)当元素个数较少且都为整数时,内部编码为intset;
internal-156.sdjnsoft.com:16379> sadd set:intset 1 2 3 4 5
(integer) 5
internal-156.sdjnsoft.com:16379> object encoding set:intset
"intset"
(2) 当元素个数超过512个,内部编码变为hashtable ;
internal-156.sdjnsoft.com:16379>sadd set:hashtable 1 2 3 4 5 6 7... 511 512 513
(integer) 513
internal-156.sdjnsoft.com:16379> object encoding set:hashtable
"hashtable"
(3) 当某个元素不为整数时,内部编码也变为hashtable;
internal-156.sdjnsoft.com:16379> sadd set:hashtable a 1 2 bv 3 4
(integer) 6
internal-156.sdjnsoft.com:16379> object encoding set:hashtable
"hashtable"
5. 有序集合的内部编码
有序集合类型的内部编码有两种:
- ziplist(压缩列表):当有序集合的元素个数小于zset-max-ziplist-entries配置(默认128个),同时每个元素的值小于zset-max-ziplist-value配置(默认64个字节)时,Redis会用ziplist来作为有序集合的内部实现,ziplist可以有效减少内存使用。
- skiplist(跳跃表):当ziplist条件不满足时,有序结合会使用skiplist作为内部实现,因为此时zip读写效率会下降。
下面来举例来说明:
(1)当元素个数较小且每个元素较小时,内部编码为ziplist
internal-156.sdjnsoft.com:16379> zadd zset:ziplist 10 a 20 b 30 c
(integer) 3
internal-156.sdjnsoft.com:16379> object encoding zset:ziplist
"ziplist"
(2) 当元素个数超过128个,内部编码变为skiplist;
...待输入...
(3) 当某个元素大于64个时,内部编码会变为skiplist;
internal-156.sdjnsoft.com:16379> zadd zset:skiplist 10 a 20 b 30 c 40 "asdasdaskdjalksnd;kjsadnbg;kjdanfg;dksnhg;fdksnhsdfkjhns;dfglkhnsdf'lhk"
(integer) 4
internal-156.sdjnsoft.com:16379> object encoding zset:skiplist
"skiplist"