Redis中所有数据都存储在对象中,Redis共有5种对象:字符串对象、列表对象、哈希对象、集合对象、有序集合对象。每一种对象都至少有两种底层编码方式。
本文介绍这5种对象的底层编码和编码转换原理。基于Redis2.9版本。
Redis对象基本知识
Redis使用对象来表示数据库中的键和值,每一个键值对都是两个对象:键作为一个对象、值作为一个对象。其中键总是一个字符串对象,值是5种对象之一:字符串对象、列表对象、哈希对象、集合对象、有序集合对象。
Redis任何一种对象都使用redisObject结构来表示:
typedef struct redisObject{
// 对象类型,五种对象之一:字符串、列表、哈希、集合、有序集合
unsigned type 4;
// 对象的编码方式,以下之一:简单动态字符串、双端链表、字典、压缩列表、整数集合
unsigned encoding 4;
// 底层数据结构指针,即指向真实存储的“值”
void *ptr;
}
从数据结构来看,每种对象至少有两种或以上的编码方式(数据结构),不同的编码可以优化不同场景的使用效率。编码方式包括:整数、简单动态字符串(SDS)、双端链表、字典、压缩列表、整数集合。
对象编码方式对使用者是透明的,即敲命令的时候不需要关心对象的编码方式,Redis将自动为即将创建的对象选择合适的编码(效率原则)。
即从编码方式来讲,Redis对象的操作命令是多态的。
多说一点,Redis命令的多态分为两种情况:
- 基于对象类型的多态–一个命令可以操作多种类型的对象。如DEL、EXPIRE。
- 基于编码类型的多态–一个命令可以处理同一种对象的多种编码。如LLEN命令可以处理
ziplist或linkedlist编码的列表对象。
Redis在执行某些命令前,先检查对象类型和指定的命令是否匹配,匹配后才能执行命令操作对象。
命令type返回的是键值对的值的对象类型,命令返回结果可以是:string、list、hash、set、zset,对应五种对象。
字符串对象的编码
字符串对象的编码有三种:int–整数、raw–简单动态字符串(Redis自定义的字符串结构)、embstr–优化的简单动态字符串。
int用来存储整数值,如果这个值可以用long类型表示,那么将使用int作为字符串对象的编码。取值范围为:-2^31 ~ (2^31 -1)。
raw用来存储长度大于39字节的字符串对象。
embstr用来存储长度小于等于39字节的字符串对象。设计embstr是为了提高内存分配效率,使用embstr编码创建字符串对象只需要分配一次内存。而int或raw编码创建字符串对象都需要分配两次内存。
所以Redis存取小于39个字节的字符串会更高效。
用户修改字符串对象时,Redis会自动判断和转换字符串的编码:
- 原
int编码的字符串,修改后不再是整数值的,编码将从int变为raw。 - 原
embstr编码的字符串,执行任何修改后,编码变为raw。 raw编码的字符串,执行任何修改后,编码始终保持不变。
字符串对象是Redis五种对象中唯一一种被其他四种对象嵌套的对象。因为字符串使用是如此频繁,所以为字符串设计了三种编码方式,力求提高字符串在不同场景下的存取效率。
列表对象的编码
从逻辑结构来讲,列表对象类似于Java的List。一个列表中可以保存多个列表元素,每个列表元素都是一个字符串对象。
列表对象的编码有两种:ziplist–压缩列表、linkedlist–双端链表。
ziplist编码:当列表中元素数量小于512个、并且所有列表元素的长度小于64字节时,使用ziplist作为列表对象的编码。
linkedlist编码:其他情况,使用linkedlist作为列表对象的编码。
用户创建或修改列表对象时,Redis会自动判断和转换列表对象的编码:
- 当满足上面说的512、64两个数字条件时,使用
ziplist编码。 - 当不满足512、64两个数字条件时,使用
linkedlist编码。
哈希对象的编码
从逻辑结构讲,哈希对象的结构与Java中的Map类似。一个哈希对象可以保存多个哈希元素,一个哈希元素(哈希键值对)由哈希键+哈希值组成,哈希键、哈希值又都是一个字符串对象。
哈希对象的编码有两种:ziplist–压缩列表、hashtable–字典。
ziplist编码:当哈希对象中元素数量小于512个、并且所有哈希元素的键和值的长度都小于64字节时,使用ziplist作为哈希对象的编码。当使用ziplist编码时,每个哈希元素的键、值是依次排列在ziplist中的,例子:
| 元素1的键 | 元素1的值 | 元素2的键 | 元素2的值 | … | … |
|---|
hashtable编码:其他情况,使用hashtable–字典作为哈希对象的编码。
用户创建或修改哈希对象时,Redis会自动判断和转换哈希对象的编码:
- 当满足上面说的512、64两个数字条件时,使用
ziplist编码。 - 当不满足512、64两个数字条件时,使用
hashtable编码。
集合对象的编码
从逻辑结构讲,集合对象的结构与Java中的Set类似。一个集合中可以保存多个不重复的集合元素。集合元素是字符串或整数(下面说明)。
集合对象的编码方式有两种:intset–整数集合、hashtable–字典。
intset编码:集合中每个元素都是整数。
hashtable编码:字典键用来存储集合元素,字典值全部为NULL。这时候每个集合元素都是一个字符串。
用户创建或修改集合对象时,Redis会自动判断和转换集合对象的编码:
- 当集合元素数量不超过512个、且所有元素都是整数时,使用
intset编码。 - 不满足上面条件时,转换为
hashtable编码。
思考题:
对于
hashtable编码的集合对象,如果删除部分元素后再次满足了intset编码的条件,集合对象的编码会再次转换为intset吗?答案:不会。
集合对象不会做这种“逆向”的编码转换,只要编码从intset转换为hashtable,就不会再转为intset了。
为什么呢?因为编码转换需要先判断转换条件,然后如果满足转换条件,则分配内存创建新对象、销毁旧对象…即编码转换是需要成本的,如果每次修改对象都做编码条件判断和转换,服务器CPU和内存分配成本太高。
同理,其他类型的Redis对象同样不会做“逆向”编码转换。
有序集合对象的编码
有序集合对象与集合对象类似,不允许有重复元素。区别是有序集合的每个元素会关联一个double类型的分数,Redis通过这个分数对元素进行从小到大对排序。
有序集合对象的编码方式有两种:ziplist–压缩列表、skiplist–字典+跳跃表的结构。
ziplist编码:使用ziplist编码的有序集合对象,每个集合元素使用挨在一起的两个压缩列表节点来保存。元素是按分数从小到大排序的。例子:
| 元素1的成员(字符串) | 元素1的分数(double) | 元素2的成员 | 元素2的分数 | … | … |
|---|
skiplist编码:使用skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset结构同时包含一个字典和一个跳跃表。其中跳跃表保存了按分值从小到大排列的所有集合元素。另外其中的字典保存了每个成员和分值的映射,从而程序可以用O(1)的复杂度查询成员的分值。
用户创建或修改有序集合对象时,Redis会自动判断和转换有序集合对象的编码:
- 集合元素个数少于128个、并且每个元素的长度都小于64字节时,使用
ziplist编码。 - 其他情况使用
skiplist编码。
参考《Redis设计与实现》黄健宏
本文深入探讨Redis中的五种对象——字符串、列表、哈希、集合和有序集合的底层编码规则,包括编码转换原理和优化策略。详细解析了不同编码方式如int、raw、embstr、ziplist、linkedlist、hashtable、intset和skiplist等在不同场景下的应用,以提升Redis的数据存储和访问效率。
4618

被折叠的 条评论
为什么被折叠?



