相信不少人应该都知道 Redis 有五种数据类型:字符串、列表、哈希、集合和有序集合。但这五种数据类型是什么含义?Redis 的数据又是怎样存储的?今天咱们一块儿来认识下 Redis 这五种数据结构的含义及其底层实现。数据库
首先要明确的是,Redis 并无直接使用这五种数据结构来实现键值对数据库,而是基于这些数据结构建立了一套对象系统,咱们常说的数据类型,准确来讲,是 Redis 对象系统的类型。缓存
对于 Redis 而言,全部键值对的存储,都是将数据存储在对象结构中。所不一样的是,键老是一个字符串对象,值能够是任意类型的对象。
对象源码结构以下:服务器
typedef struct redisObject { unsigned type:4; // 对象类型 unsigned encoding:4; // 对象编码 unsigned lru:LRU_BITS; // LRU int refcount; // 引用统计 void *ptr; // 指向底层实现数据结构的指针 } robj;
对象有五种数据类型,就是咱们上面提过的:数据结构
结合咱们上面提到的键值对存储类型的差异,能够了解到,咱们常说的“一个列表键或一个哈希键”,本质上指的是:一个 key 对应的 value 是列表对象或哈希对象。函数
对于 type 字段,咱们可使用 TYPE
命令来查看指定 key 对应 value 值的对象类型。
优化
按道理讲,已经有了 type,为何还要搞个编码呢?ui
想一想看,经过 encoding 属性,咱们是否是使用不一样编码的对象?这种使用方式能够根据不一样的使用场景来为一个对象设置不一样的编码,从而优化在某一场景下的效率,极大的提高了 Redis 的灵活性和效率。编码
举个栗子,在列表对象包含的元素比较少时,Redis 使用压缩列表做为列表对象的底层实现:指针
后面介绍完编码类型后,咱们会详细认识不一样类型对应的各个编码方式。
encoding 属性有如下取值:
对象的编码类型能够由 OBJECT ENCODING
命令获取。
OBJECT ENCODING
命令对应源码以下:
# src/object.c char *strEncoding(int encoding) { switch(encoding) { case OBJ_ENCODING_RAW: return "raw"; case OBJ_ENCODING_INT: return "int"; case OBJ_ENCODING_HT: return "hashtable"; case OBJ_ENCODING_QUICKLIST: return "quicklist"; case OBJ_ENCODING_ZIPLIST: return "ziplist"; case OBJ_ENCODING_INTSET: return "intset"; case OBJ_ENCODING_SKIPLIST: return "skiplist"; case OBJ_ENCODING_EMBSTR: return "embstr"; default: return "unknown"; } }
OBJECT ENCODING
命令输出值与 encoding 属性取值对应关系以下:
| 对象使用的底层数据结构 | 编码常量 | OBJECT ENCODING 输出 |
| :-: | :-: | :-: |
| 简单动态字符串 |OBJ_ENCODING_RAW |"raw" |
| 整数 |OBJ_ENCODING_INT |"int" |
| embstr 编码的简单动态字符串 |OBJ_ENCODING_EMBSTR |"embstr" |
| 字典 |OBJ_ENCODING_HT |"hashtable" |
| 压缩列表 |OBJ_ENCODING_ZIPLIST |"ziplist" |
| 快速列表 |OBJ_ENCODING_QUICKLIST |"quicklist" |
| 整数集合 |OBJ_ENCODING_INTSET |"intset" |
| 跳跃表 |OBJ_ENCODING_SKIPLIST |"skiplist" |
总结来看,以下图:
十一种不一样编码的对象分别是:
接下来,咱们将对上述十一种对象一一介绍。以后再一一认识对象编码。
字符串对象的可选编码分别是:int、raw 或者 embstr。
若是一个字符串对象保存的是整数值,而且这个整数值能够用 long 类型表示,那么字符串对象会将整数值保存在字符串对象结构的 ptr 属性中,并将字符串对象的编码设置为 int。
咱们执行如下 SET 命令,服务器将建立一个以下图所示的 int 编码的字符串对象做为 num 键的值:
# redis-cli 127.0.0.1:6380> set num 12345 OK 127.0.0.1:6380> OBJECT ENCODING num "int"
若是字符串对象保存的是一个字符串值,而且这个字符串值的长度大于 44 字节(根据版本的不一样,这个值会有差别。详见 object.c 文件中的 OBJ_ENCODING_EMBSTR_SIZE_LIMIT 常量),那么字符串对象将使用**简单动态字符串(SDS)来保存这个字符串值,并将对象的编码设置为 raw。
咱们执行下面的 SET 命令,服务器将建立一个图 7 所示的 raw 编码的字符串对象做为 k1 键的值(45 字节):
127.0.0.1:7379> set story 'k01234567890123456789012345678901234567890123' OK 127.0.0.1:7379> OBJECT ENCODING k4 "raw"
若是字符串保存的是一个字符串值,而且这个字符串值的长度小于等于 44 字节(根据版本的不一样,这个值会有差别。详见 object.c 文件中的 OBJ_ENCODING_EMBSTR_SIZE_LIMIT 常量),那么字符串对象将使用 embstr 编码的方式来保存这个字符串。
embstr 编码是专门用于保存段字符串的一种优化编码方式,这种编码和 raw 编码同样,都使用 redisObject 和 sdshdr 结构来表示字符串对象。但和 raw 编码的字符串对象不一样的是:
相对应的,释放内存时,embstr 编码的对象也只需调用一次内存释放函数。
所以,使用 embstr 编码的字符串对象来保存短字符串值有如下好处:
如下命令建立了一个 embstr 编码的字符串对象做为 msg 键的值,值对象结构如图 8。
127.0.0.1:6380> SET msg hello OK 127.0.0.1:6380> OBJECT ENCODING msg "embstr"
Redis 中,long double 类型的浮点数也是做为字符串值来保存的。
咱们要保存一个浮点数到字符串对象中,程序会先将这个浮点数转换成字符串值,而后再保存转换所得的字符串值。
执行如下代码,将建立一个包含 3.14 的字符串表示 "3.14" 的字符串对象:
127.0.0.1:6380> SET pi 3.14 OK 127.0.0.1:6380> OBJECT ENCODING pi "embstr"
在有须要的时候,程序会将保存在字符串对象里的字符串值转换成浮点数值,执行某些操做,而后将所得的浮点数值转换回字符串值,继续保存在字符串对象中。
好比,咱们对 pi 键执行如下操做:
127.0.0.1:6380> INCRBYFLOAT pi 2.0 "5.14" 127.0.0.1:6380> OBJECT ENCODING pi "embstr"
执行 INCRBYFLOAT
命令过程当中,实际上就会出现字符串与浮点数值互相转换的状况。
int 编码的字符串对象和 embstr 编码的字符串对象在知足某些条件的状况下,会被转换为 raw 编码的字符串对象。
对于 int 编码的字符串对象来讲,若是咱们在执行命令后,使得这个对象保存的再也不是整数值,而是一个字符串,那么字符串对象就会从 int 变为 raw。好比 APPEND
命令等。
另外,对于 embstr 编码的字符串,因为 Redis 没有为其编写任何相应的修改程序,因此 embstr 编码的字符串对象其实是只读的。当咱们对 embstr 编码的字符串对象执行任何修改命令时,程序都会先将对象的编码从 embstr 转换成 raw。也就是说,embstr 编码的字符串一旦修改,必定会转换成 raw 编码的字符串对象。
对于字符串对象各个编码的状况,总结以下:
| 值 | 编码|
| :-- | :-- |
| 能够用 long 表示的整数值 | int |
| 能够用 long double 保存的浮点数 | raw 或 embstr |
| 不能够用 long 或 long double 表示的整数或小数值 | raw 或 embstr |
| 大于 44 字节的字符串 | raw |
| 小于或等于 44 字节的字符串 | embstr |
列表对象的可选编码分别是:quicklist(3.2 版本前是 ziplist 和 linkedlist)。
3.2 版本引入了 quicklist 编码,此编码结合了 ziplist 和 linkedlist,使用双向链表的形式,在每一个节点上存储一个 ziplist,而每一个 ziplist 又能够存储多个键值对。也就是说,quicklist 每一个节点上存储的不是一个数据,而是一片数据。
执行如下命令,服务器将会建立一个列表对象,quicklist 结构如图 8 所示:
127.0.0.1:7379> RPUSH animal 'dog' 'cat' 'pig' (integer) 3 (5.12s) 127.0.0.1:7379> OBJECT ENCODING animal "quicklist"