前言
Redis 提供了许多的数据类型,其中最常用的有以下五种:String(字符串),list(列表),hash(哈希),set(集合),zset(有序集合),但这些只是 Redis 对外的数据结构。
Redis 的 5 种数据类型
ps:有序集合相当于除了存储数据以外,还需要存储一个对应的权值
Redis 底层在实现上述数据结构时,会在源码层面,针对上述数据结构的实现进行特定的优化,来达到节省时间或空间的效果,同一个数据类型,在底层的编码实现方式可能是不同的,会根据特定的场景优化
实际上 Redis 针对每种数据结构都有自己的底层内部编码实现,⽽且是多种实现,这样 Redis 会在合适的场景选择合适的内部编码
Redis 数据结构和内部编码
String
对于 String 类型,Redis 内部编码实现有 raw ,int,embstr 三种方式,其中 raw 编码用于保存最基本的字符串(底层就是一个 char 数组或是一个 Byte 数组)。
当 value 是一个整数时,虽然表面上 Redis 提供的是 String 类型,但底层用的是 int 来保存这个整数。
当 value 是一个短字符串时,底层就使用 embstr 编码来保存数据,embstr 根据短字符串进行了特殊的优化
hash
当 value 是哈希类型时,如果存储的键值对不是很多,真正在底层去实现一个哈希表就会造成资源浪费,所以此时实际底层编码并没有真正的实现一个哈希表,编码方式是 ziplist,实现了一个压缩列表,节省了资源
当哈希表中存储的键值对较多时,就会采用 hashtable 的编码方式,在底层真正的实现一个哈希表来存储数据,虽然 Redis 中哈希表的实现可能和我们了解到的有所区别,但核心的思想是一致的
list
和 hash 数据类型相似,当列表中存储的数据较少时,底层的编码方式就是 ziplist ,实现了一个压缩列表,节省了资源
当存储的数据较多时底层编码方式就是 linkedlist ,真正的实现了一个链表来存储数据
从 Redis 3.2 开始,引入了新的实现方式 quicklist ,同时兼顾了 linkedlist 和 ziplist 的优点,quicklist 就是一个链表,但每个元素又是一个 ziplist ,把空间和效率都兼顾到
set
当集合中存的都是整数时,底部的编码实现就是 intset
普通的编码实现方式就是 hashtable 实现一个哈希表(集合底层的数据结构本质上就是哈希表,这是存储的数据不是 key-value 的键值对,只是 key 值而已)
zset
在有序列表中存储的数据较少时,底层的编码方式也是 ziplist,实现了一个压缩列表,节省了资源
skiplist 是一个跳表,跳表也是一个链表,不同于普通的链表,每个节点上有多个指针域,巧妙的搭配这些指针域的指向就可以做到,从跳表上查询数据的复杂度是 O(logN)
结语
可以看到每种数据结构都有⾄少两种以上的内部编码实现,同时有些内部编码,例如 ziplist,可以作为多种数据结构的内部实现,可以通过 object encoding 命令查询 value 对应的内部编码:
按道理通过 set 设置的键值对 value 的类型应该是 String,但内部编码的实现是 int,因为 value 对应的数据是 111, redis 对其底层的编码进行了优化,通过 int 类型来保存数据。
Redis 这样设计的好处
1.可以改进内部编码,⽽对外的数据结构和命令没有任何影响,这样⼀旦开发出更优秀的内部编码, ⽆需改动外部数据结构和命令,例如 Redis 3.2 提供了 quicklist,结合了 ziplist和 linkedlist 两者的优势,为列表类型提供了⼀种更为优秀的内部编码实现,⽽对⽤户来说基本⽆感知。
2.多种内部编码实现可以在不同场景下发挥各⾃的优势,例如 ziplist ⽐较节省内存,但是在列表元素⽐较多的情况下,性能会下降,这时候 Redis 会根据配置选项将列表类型的内部实现转换为 linkedlist,整个过程⽤户同样⽆感知。