相信很多人应该都知道 Redis 有五种数据类型:字符串、列表、哈希、集合和有序集合。但这五种数据类型是什么含义?Redis 的数据又是怎样存储的?今天我们一起来认识下 Redis 这五种数据结构的含义及其底层实现。

首先要明确的是,Redis 并没有直接使用这五种数据结构来实现键值对数据库,而是基于这些数据结构创建了一套对象系统,我们常说的数据类型,准确来说,是 Redis 对象系统的类型。

1 对象

对于 Redis 而言,所有键值对的存储,都是将数据存储在对象结构中。所不同的是,键总是一个字符串对象,值可以是任意类型的对象

对象源码结构如下:

typedef struct redisObject {
unsigned type:4; // 对象类型
unsigned encoding:4; // 对象编码
unsigned lru:LRU_BITS; // LRU
int refcount; // 引用统计
void *ptr; // 指向底层实现数据结构的指针
} robj;
  • type 字段:对象类型,就是我们常说的。string、list、hash、set、zset。
  • encoding:对象编码。也就是我们上面说的底层数据结构。
  • LRU:键值对的 LRU。
  • refcount:键值对对象的引用统计。当此值为 0 时,回收对象。
  • *ptr:指向底层实现数据结构的指针。就是实际存放数据的地址。

1.2 对象类型

对象有五种数据类型,就是我们上面提过的:

  1. 字符串类型
  2. 列表类型
  3. 哈希类型
  4. 集合类型
  5. 有序集合类型

结合我们上面提到的键值对存储类型的差别,可以了解到,我们常说的“一个列表键或一个哈希键”,本质上指的是:一个 key 对应的 value 是列表对象或哈希对象

对于 type 字段,我们可以使用 TYPE 命令来查看指定 key 对应 value 值的对象类型。



1.3 对象编码

按道理讲,已经有了 type,为什么还要搞个编码呢?

想想看,通过 encoding 属性,我们是不是使用不同编码的对象?这种使用方式可以根据不同的使用场景来为一个对象设置不同的编码,从而优化在某一场景下的效率,极大的提升了 Redis 的灵活性和效率。

举个栗子,在列表对象包含的元素比较少时,Redis 使用压缩列表作为列表对象的底层实现:

  • 压缩列表比快速链表更节约内存,并且在元素数量较少时,在内存中以连续块方式报错的压缩列表比起快速列表可以更快的载入到缓存中;
  • 随着列表对象包含的元素越来越多,使用压缩列表保存元素的优势消失时,对象就会将底层实现从压缩列表转为功能更强、也更适合保存大量元素的快速链表。

后面介绍完编码类型后,我们会详细认识不同类型对应的各个编码方式。

encoding 属性有以下取值:

  1. OBJ_ENCODING_RAW
  2. OBJ_ENCODING_INT
  3. OBJ_ENCODING_HT
  4. OBJ_ENCODING_QUICKLIST
  5. OBJ_ENCODING_ZIPLIST
  6. OBJ_ENCODING_INTSET
  7. OBJ_ENCODING_SKIPLIST
  8. OBJ_ENCODING_EMBSTR

对象的编码类型可以由 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"

总结来看,如下图:

十一种不同编码的对象分别是:

  1. 使用双端或快速列表实现的列表对象
  2. 使用压缩列表实现的列表对象
  3. 使用字典实现的哈希对象
  4. 使用压缩列表实现的哈希对象
  5. 使用字典实现的集合对象
  6. 使用整数集合实现的集合对象
  7. 使用压缩列表实现的有序集合对象
  8. 使用跳跃表实现的有序集合对象
  9. 使用普通 SDS 实现的字符串对象
  10. 使用 embstr 编码的 SDS 实现的字符串对象
  11. 使用整数值实现的字符串对象

接下来,我们将对上述十一种对象一一介绍。之后再一一认识对象编码。

2 字符串对象

字符串对象的可选编码分别是:int、raw 或者 embstr。

2.1 int 编码的字符串对象

如果一个字符串对象保存的是整数值,并且这个整数值可以用 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"

2.2 raw 编码的字符串对象

如果字符串对象保存的是一个字符串值,并且这个字符串值的长度大于 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"

2.3 embstr 编码的字符串对象

如果字符串保存的是一个字符串值,并且这个字符串值的长度小于等于 44 字节(根据版本的不同,这个值会有差异。详见 object.c 文件中的 OBJ_ENCODING_EMBSTR_SIZE_LIMIT 常量),那么字符串对象将使用 embstr 编码的方式来保存这个字符串。

embstr 编码是专门用于保存段字符串的一种优化编码方式,这种编码和 raw 编码一样,都使用 redisObject 和 sdshdr 结构来表示字符串对象。但和 raw 编码的字符串对象不同的是:

  • raw 编码会调用两次内存分配函数来分别创建 redisObject 和 sdshdr 结构
  • embstr 编码通过一次内存分配函数分配一块连续的空间,空间中依次包含 redisObject 和 sdsHdr 两个结构。

相对应的,释放内存时,embstr 编码的对象也只需调用一次内存释放函数。

因此,使用 embstr 编码的字符串对象来保存短字符串值有以下好处:

  • 创建字符串对象时,内存分配次数从两次降低为一次。
  • 释放 embstr 编码的字符串对象时,调用内存释放函数的次数从两次降低为一次。
  • 更好地利用缓存优势。embstr 编码的字符串对象的所有数据都保存在一块连续的内存中 ,这种方式比 raw 编码的字符串对象能够更好的利用缓存带来的优势。

以下命令创建了一个 embstr 编码的字符串对象作为 msg 键的值,值对象结构如图 8。

127.0.0.1:6380> SET msg hello
OK
127.0.0.1:6380> OBJECT ENCODING msg
"embstr"

2.4 浮点数编码

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 命令过程中,实际上就会出现字符串与浮点数值互相转换的情况。

2.5 编码转换

int 编码的字符串对象和 embstr 编码的字符串对象在满足某些条件的情况下,会被转换为 raw 编码的字符串对象。

对于 int 编码的字符串对象来说,如果我们在执行命令后,使得这个对象保存的不再是整数值,而是一个字符串,那么字符串对象就会从 int 变为 raw。比如 APPEND 命令等。

另外,对于 embstr 编码的字符串,由于 Redis 没有为其编写任何相应的修改程序,所以 embstr 编码的字符串对象实际上是只读的。当我们对 embstr 编码的字符串对象执行任何修改命令时,程序都会先将对象的编码从 embstr 转换成 raw。也就是说,embstr 编码的字符串一旦修改,一定会转换成 raw 编码的字符串对象

2.6 值与编码对应关系

对于字符串对象各个编码的情况,总结如下:

编码
可以用 long 表示的整数值 int
可以用 long double 保存的浮点数 raw 或 embstr
不可以用 long 或 long double 表示的整数或小数值 raw 或 embstr
大于 44 字节的字符串 raw
小于或等于 44 字节的字符串 embstr

3 列表对象

列表对象的可选编码分别是:quicklist(3.2 版本前是 ziplist 和 linkedlist)。

3.1 quicklist 编码的列表对象

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"

总结

  1. Redis 自己实现了一套对象系统来实现所有功能。
  2. 对象有对象类型对象编码
  3. 对象类型对应字符串、列表、哈希、集合、有序集合五种
  4. 对象编码对应跳跃表、压缩列表、集合、动态字符串等八种底层数据结构

跟着大彬读源码 - Redis 5 - 对象和数据类型(上)的更多相关文章

  1. 跟着大彬读源码 - Redis 6 - 对象和数据类型(下)

    继续撸我们的对象和数据类型. 上节我们一起认识了字符串和列表,接下来还有哈希.集合和有序集合. 1 哈希对象 哈希对象的可选编码分别是:ziplist 和 hashtable. 1.1 ziplist ...

  2. 跟着大彬读源码 - Redis 7 - 对象编码之简单动态字符串

    Redis 没有直接使用 C 语言传统的字符串表示(以空字符串结尾的字符数组),而是构建了一种名为简单动态字符串(simple dynamic string)的抽象类型,并将 SDS 用作 Redis ...

  3. 跟着大彬读源码 - Redis 8 - 对象编码之字典

    目录 1 字典的实现 2 插入算法 3 rehash 与 渐进式 rehash 总结 字典,是一种用于保存键值对的抽象数据结构.由于 C 语言没有内置字典这种数据结构,因此 Redis 构建了自己的字 ...

  4. 跟着大彬读源码 - Redis 9 - 对象编码之 三种list

    目录 1 ziplist 2 skiplist 3 quicklist 总结 Redis 底层使用了 ziplist.skiplist 和 quicklist 三种 list 结构来实现相关对象.顾名 ...

  5. 跟着大彬读源码 - Redis 10 - 对象编码之整数集合

    [TOC] 整数集合是 Redis 集合键的底层实现之一.当一个集合只包含整数值元素,并且元素数量不多时,Redis 就会使用整数集合作为集合键的底层实现. 1 整数集合的实现 整数集合是 Redis ...

  6. 跟着大彬读源码 - Redis 1 - 启动服务,程序都干了什么?

    一直很羡慕那些能读 Redis 源码的童鞋,也一直想自己解读一遍,但迫于 C 大魔王的压力,解读日期遥遥无期. 相信很多小伙伴应该也都对或曾对源码感兴趣,但一来觉得自己不会 C 语言,二来也不知从何入 ...

  7. 跟着大彬读源码 - Redis 2 - 服务器如何响应客户端请求?(上)

    上次我们通过问题"启动服务器,程序都干了什么?",跟着源码,深入了解了 Redis 服务器的启动过程. 既然启动了 Redis 服务器,那我们就要连上 Redis 服务干些事情.这 ...

  8. 跟着大彬读源码 - Redis 3 - 服务器如何响应客户端请求?(下)

    继续我们上一节的讨论.服务器启动了,客户端也发送命令了.接下来,就要到服务器"表演"的时刻了. 1 服务器处理 服务器读取到命令请求后,会进行一系列的处理. 1.1 读取命令请求 ...

  9. 跟着大彬读源码 - Redis 4 - 服务器的事件驱动有什么含义?(上)

    众所周知,Redis 服务器是一个事件驱动程序.那么事件驱动对于 Redis 而言有什么含义?源码中又是如何实现事件驱动的呢?今天,我们一起来认识下 Redis 服务器的事件驱动. 对于 Redis ...

随机推荐

  1. WPF Build Action

    None: The file is not included in the project output group and is not compiled in the build process. ...

  2. SQL Server 将某一列的值拼接成字符串

    名称 海鲜水产 水果蔬菜 海参 肉禽蛋 牛排 腊味 生鲜食品 将以上一列变成: 生鲜食品,海鲜水产,水果蔬菜,海参,牛排,肉禽蛋,腊味 sql for xml path('')

  3. ASP.NET MVC5快速入门--MyFirstWeb并发布到Windows Azure上

    博主刚刚学习ASP.NET MVC5,看着微软的文档一点点学,就把FirstWeb的建立展示一下下啦,本次建立一个带个人身份验证的例子(即有注册登录机制的动态网页),开始,啦啦啦~~ 新建一个项目,选 ...

  4. 任何一件事,如果你不投入时间和精力去驯养,就不可能产生真正的兴趣和热爱(Focus Feedback FixIt的原理) good

    这两本书和我们说的兴趣结合起来,为我们指明了精进的道路: 选择一个你感兴趣的方向 刻意练习 持续投入时间和精力 所谓刻意练习,简单说就是“3F”,即: Focus Feedback Fix it Fo ...

  5. 京东sdk商家上架接口调用问题总结

    前言: 最近在做商家发布产品,调用京东sdk,发现问题很多,而且还是在我同事的帮助下完成的,摸索中,菜鸟还请高手门多多提携才好,入正题 首先是引用jd的sdk啦,京东sdk中发布商品需要调用一个 36 ...

  6. ACL 我为什么要发明一个轮子?

    现在成熟的开发库与开发框架有很多,所以平时我们在开发自己的应用程序时一般直接拿来用就可以了,所以当我先是开发出 C 语言版的 acl 框架库时有人认为是这个轮子是否值得发明,而当我再开发出基于 acl ...

  7. 【Web前端Talk】“用数据说话,从埋点开始”-带你理解前端的三种埋点

    埋点到底是什么呢? 引用自百科的原话是,埋点分析网站分析的一种常用的数据采集方法.因此其本质是分析,但是靠什么分析呢?靠埋点得到的数据.通俗来讲,就是当我想要在某个产品上得到用户的一些行为数据用来分析 ...

  8. RocketMQ(5)---RocketMQ重试机制

    RocketMQ重试机制 消息重试分为两种:Producer发送消息的重试 和 Consumer消息消费的重试. 一.Producer端重试 Producer端重试是指: Producer往MQ上发消 ...

  9. node实现文件拷贝2

    https://www.cnblogs.com/coding4/p/7495968.html 文件拷贝NodeJS 提供了基本的文件操作 API,但是像文件拷贝这种高级功能就没有提供,因此我们先拿文件 ...

  10. 并发编程-concurrent指南-原子操作类-AtomicReference

    1.类 AtomicReference<V> public class AtomicReference<V>extends Objectimplements Serializa ...