Redis对象的底层编码规则

本文深入探讨Redis中的五种对象——字符串、列表、哈希、集合和有序集合的底层编码规则,包括编码转换原理和优化策略。详细解析了不同编码方式如int、raw、embstr、ziplist、linkedlist、hashtable、intset和skiplist等在不同场景下的应用,以提升Redis的数据存储和访问效率。

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命令的多态分为两种情况:

  1. 基于对象类型的多态–一个命令可以操作多种类型的对象。如DEL、EXPIRE。
  2. 基于编码类型的多态–一个命令可以处理同一种对象的多种编码。如LLEN命令可以处理ziplistlinkedlist编码的列表对象。

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编码创建字符串对象只需要分配一次内存。而intraw编码创建字符串对象都需要分配两次内存。

所以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设计与实现》黄健宏

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值