通过上几节,我们了解到Redis底层的一些重要的数据结构,比如简单动态字符串、链表、字典、跳跃表等等。
除了这几个还有整数集合,压缩列表。会在本章进行简单的描述。
而Redis,是基于上述几种数据结构创建了对象,再由对象构建Redis数据库,所以每个对象至少用了一种上述的数据结构。
这个对象有字符串对象、列表对象、哈希对象、集合对象和有序集合对象。
Redis 对象的类型与编码
Redis 使用对象来表示数据库中的键和值, 每次当我们在 Redis 的数据库中新创建一个键值对时, 我们至少会创建两个对象, 一个对象用作键值对的键(键对象), 另一个对象用作键值对的值(值对象)。
Redis 中的每个对象都由一个 redisObject
结构表示, 该结构中和保存数据有关的三个属性分别是 type
属性、 encoding
属性和 ptr
属性:
typedef struct redisObject { // 类型 unsigned type:4; // 编码 unsigned encoding:4; // 指向底层实现数据结构的指针 void *ptr; // ... } robj;
类型
对象的 type
属性记录了对象的类型,如下表格所示:
类型常量 | 对象名称 |
REDIS_STRING | 字符串对象 |
REDIS_LIST | 列表对象 |
REDIS_HASH | 哈希对象 |
REDIS_SET | 集合对象 |
REDIS_ZSET | 有序集合对象 |
对Redis数据库来说,它的键总是字符串对象,而值可以为上表格中任意一种。
所以当我们用 type 命令获取键的类型时,返回的是键对应值对象的类型:
编码
对象的 ptr
指针指向对象的底层实现数据结构, 而这些数据结构由对象的 encoding
属性决定。
encoding
属性记录了对象所使用的编码, 也即是说这个对象使用了什么数据结构作为对象的底层实现,下图为编码对应底层数据结构表:
编码常量 | 编码对应的底层数据结构 |
REDIS_ENCODING_INT | long 类型的整数 |
REDIS_ENCODING_EMBSTR | embstr 编码的简单动态字符串 |
REDIS_ENCODING_RAW | 简单动态字符串 |
REDIS_ENCODING_HT | 字典 |
REDIS_ENCODING_LINKEDLIST | 双端链表 |
REDIS_ENCODING_ZIPLIST | 压缩列表 |
REDIS_ENCODING_INTSET | 整数集合 |
REDIS_ENCODING_SKIPLIST | 跳跃表和字典 |
每种类型的对象都至少使用了两种不同的编码, 如下列表列出了每种类型的对象可以使用的编码:
类型 | 编码 | 对象 |
REDIS_STRING | REDIS_ENCODING_INT | 使用整数值实现的字符串对象 |
REDIS_STRING | REDIS_ENCODING_EMBSTR | 使用 embstr 编码的简单动态字符串实现的字符串对象。 |
REDIS_STRING | REDIS_ENCODING_RAW | 使用简单动态字符串实现的字符串对象 |
REDIS_LIST | REDIS_ENCODING_ZIPLIST | 使用压缩列表实现的列表对象 |
REDIS_LIST | REDIS_ENCODING_LINKEDLIST | 使用双端链表实现的列表对象 |
REDIS_HASH | REDIS_ENCODING_ZIPLIST | 使用压缩列表实现的哈希对象 |
REDIS_HASH | REDIS_ENCODING_HT | 使用字典实现的哈希对象 |
REDIS_SET | REDIS_ENCODING_INTSET | 使用整数集合实现的集合对象 |
REDIS_SET | REDIS_ENCODING_HT | 使用字典实现的集合对象 |
REDIS_ZSET | REDIS_ENCODING_ZIPLIST | 使用压缩列表实现的有序集合对象 |
REDIS_ZSET | REDIS_ENCODING_SKIPLIST | 使用跳跃表和字典实现的有序集合对象 |
使用 OBJECT ENCODING 命令可以查看一个数据库键的值对象的编码:
字符串对象
字符串对象的编码可以是 int
、 raw
或者 embstr
。
如果一个字符串对象保存的是整数值, 并且这个整数值可以用 long
类型来表示, 那么字符串对象的编码为 int
。
例如:
如果字符串对象保存的是一个字符串值, 并且这个字符串值的长度大于 39
字节, 那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值, 并将对象的编码设置为 raw
。
例如:
如果字符串对象保存的是一个字符串值, 并且这个字符串值的长度小于等于 39
字节, 那么字符串对象将使用 embstr
编码的方式来保存这个字符串值。
embstr
编码是专门用于保存短字符串的一种优化编码方式, 这种编码和 raw
编码一样, 都使用 redisObject
结构和 sdshdr
结构来表示字符串对象, 但 raw
编码会调用两次内存分配函数来分别创建 redisObject
结构和 sdshdr
结构, 而 embstr
编码则通过调用一次内存分配函数来分配一块连续的空间, 空间中依次包含 redisObject
和 sdshdr
两个结构。如下图结构:
embstr
编码的优点:embstr
编码将创建字符串对象所需的内存分配、内存释放次数从 raw
编码的两次降低为一次。
编码转换
int
编码的字符串对象和 embstr
编码的字符串对象在条件满足的情况下, 会被转换为 raw
编码的字符串对象。
对于 int
编码的字符串对象来说, 如果我们向对象执行了一些命令, 使得这个对象保存的不再是整数值, 而是一个字符串值, 那么字符串对象的编码将从 int
变为 raw
。
对于 embstr
编码的字符串对象来说, 因为 Redis 没有为 embstr
编码的字符串对象编写任何相应的修改程序,所以 embstr
编码的字符串对象实际上是只读的。
当我们对 embstr
编码的字符串对象执行任何修改命令时, 程序会先将对象的编码从 embstr
转换成 raw
, 然后再执行修改命令。
列表对象
列表对象的编码可以是 ziplist
或者 linkedlist
。
ziplist
编码的列表对象使用压缩列表作为底层实现, 每个压缩列表节点(entry)保存了一个列表元素。
如下例子:
如果 golang键的值对象使用的是 ziplist
编码,则列表对象在内存中的结构:
如果 golang键的值对象使用的是 linkedlist
编码,则列表对象在内存中的结构:
编码转换
当列表对象可以同时满足以下两个条件时, 列表对象使用 ziplist
编码:
- 列表对象保存的所有字符串元素的长度都小于
64
字节; - 列表对象保存的元素数量小于
512
个;
不能满足这两个条件的列表对象需要使用 linkedlist
编码。
哈希对象
哈希对象的编码可以是 ziplist
或者 hashtable
。
如下例子:
如果 profile
键的值对象使用的是 ziplist
编码,那么哈希对象在内存中的结构为:
如果 profile
键的值对象使用的是 hashtable 编码,那么哈希对象在内存中的结构为:
编码转换
当哈希对象可以同时满足以下两个条件时, 哈希对象使用 ziplist
编码:
- 哈希对象保存的所有键值对的键和值的字符串长度都小于
64
字节; - 哈希对象保存的键值对数量小于
512
个;
不能满足这两个条件的哈希对象需要使用 hashtable
编码。、
集合对象
集合对象的编码可以是 intset
或者 hashtable
。
intset
编码的集合对象使用整数集合作为底层实现, 集合对象包含的所有元素都被保存在整数集合里面。
如下面的例子:
则使用intset编码的集合对像在内存中的结构为:
hashtable
编码的集合对象使用字典作为底层实现, 字典的每个键都是一个字符串对象, 每个字符串对象包含了一个集合元素, 而字典的值则全部被设置为 NULL
。
如下图例子:
则使用 hashtable 编码的集合对像在内存中的结构为:
编码转换
当集合对象可以同时满足以下两个条件时, 对象使用 intset
编码:
- 集合对象保存的所有元素都是整数值;
- 集合对象保存的元素数量不超过
512
个;
不能满足这两个条件的集合对象需要使用 hashtable
编码。
有序集合对象
有序集合的编码可以是 ziplist
或者 skiplist
。
ziplist
编码的有序集合对象使用压缩列表作为底层实现, 每个集合元素使用两个紧挨在一起的压缩列表节点来保存, 第一个节点保存元素的成员(member), 而第二个元素则保存元素的分值(score)。
压缩列表内的集合元素按分值从小到大进行排序, 分值较小的元素被放置在靠近表头的方向, 而分值较大的元素则被放置在靠近表尾的方向。
如下图例子:
如果 language 键的值对象使用的是 ziplist
编码,那么他在内存中的结构为:
如果 language 键的值对象使用的是 skiplist
编码,那么他在内存中的结构可参考:
https://www.cnblogs.com/hulunbao/p/13963881.html
编码的转换
当有序集合对象可以同时满足以下两个条件时, 对象使用 ziplist
编码:
- 有序集合保存的元素数量小于
128
个; - 有序集合保存的所有元素成员的长度都小于
64
字节;
不能满足以上两个条件的有序集合对象将使用 skiplist
编码。
参考文献
Redis设计与实现第二版
https://www.cnblogs.com/lizhenghn/p/5322887.html