概 述

我们平时用 Redis都是处于用户层面,我们可能会不加思索地操作一个 key-value 对来方便地存取数据,感觉方便之至。但你知道这些数据在背后是如何存储以及编码的吗? 了解清楚了这个问题,将对我们更加高效地使用 Redis具有指导意义。本文开始我们将结合 Redis源码来逐个探讨Redis五大数据类型的内部编码机制。

  • 实验环境:Redis 4.0.10
注: 本文首发于 My 公众号 CodeSheep ,可 长按 或 扫描 下面的 小心心 来订阅 ↓ ↓ ↓

Redis 数据类型内部编码概况

对于 Redis的常用 5 种数据类型(String、Hash、List、Set、sorted set),每种数据类型都提供了 最少两种 内部的编码格式,而且每个数据类型内部编码方式的选择 对用户是完全透明的,Redis会根据数据量自适应地选择较优化的内部编码格式。

如果想查看某个键的内部编码格式,可以使用 OBJECT ENCODING keyname指令来进行,比如:

127.0.0.1:6379> 127.0.0.1:6379> set foo barOK127.0.0.1:6379> 127.0.0.1:6379> object encoding foo  // 查看某个Redis键值的编码"embstr"127.0.0.1:6379> 127.0.0.1:6379> 

Redis 的每个键值内部都是使用一个名字叫做 redisObject 这个 C语言结构体保存的,其代码如下:

解释如下:

  • type:表示键值的数据类型,包括 String、List、Set、ZSet、Hash

  • encoding:表示键值的内部编码方式,从 Redis源码看目前取值有如下几种:
    #define OBJ_ENCODING_RAW 0        /* Raw representation */    #define OBJ_ENCODING_INT 1        /* Encoded as integer */    #define OBJ_ENCODING_HT 2         /* Encoded as hash table */    #define OBJ_ENCODING_ZIPMAP 3     /* Encoded as zipmap */    #define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */    #define OBJ_ENCODING_ZIPLIST 5    /* Encoded as ziplist */    #define OBJ_ENCODING_INTSET 6     /* Encoded as intset */    #define OBJ_ENCODING_SKIPLIST 7   /* Encoded as skiplist */    #define OBJ_ENCODING_EMBSTR 8     /* Embedded sds string encoding */    #define OBJ_ENCODING_QUICKLIST 9  /* Encoded as linked list of ziplists */
  • refcount:表示该键值被引用的数量,即一个键值可被多个键引用

本文我们就从 Redis最基本的 String类型的内部编码开始探讨!

String 类型的内部编码情况

字符串是 Redis最基本的数据类型,Redis 中字符串对象的编码可以是 int, raw 或者 embstr 中的某一种,分别介绍如下:

  • int 编码:保存long 型的64位有符号整数

  • embstr 编码:保存长度小于44字节的字符串

  • raw 编码:保存长度大于44字节的字符串

我们不妨来做个实验实际看一下:

实际情况就是 Redis 内部会根据用户给的不同键值而使用不同的编码格式,而这一切对用户完全透明!

Redis 是使用 SDS(“简单动态字符串”)这个结构体来存储字符串,代码里定义了 5种 SDS结构体:

struct __attribute__ ((__packed__)) sdshdr5 {    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */    char buf[];};struct __attribute__ ((__packed__)) sdshdr8 {    uint8_t len; /* used */    uint8_t alloc; /* excluding the header and null terminator */    unsigned char flags; /* 3 lsb of type, 5 unused bits */    char buf[];};struct __attribute__ ((__packed__)) sdshdr16 {    uint16_t len; /* used */    uint16_t alloc; /* excluding the header and null terminator */    unsigned char flags; /* 3 lsb of type, 5 unused bits */    char buf[];};struct __attribute__ ((__packed__)) sdshdr32 {    uint32_t len; /* used */    uint32_t alloc; /* excluding the header and null terminator */    unsigned char flags; /* 3 lsb of type, 5 unused bits */    char buf[];};struct __attribute__ ((__packed__)) sdshdr64 {    uint64_t len; /* used */    uint64_t alloc; /* excluding the header and null terminator */    unsigned char flags; /* 3 lsb of type, 5 unused bits */    char buf[];};

可以看出,除了结构体字段数据类型的不同,其字段含义相差无几,其中:

  • len:字符串的长度(实际使用的长度)

  • alloc:分配内存的大小

  • flags:标志位,低三位表示类型,其余五位未使用

  • buf:字符数组

了解了这些基本的数据结构以后,我们就来看看上面例子中:

  • set foo 123

  • set foo abc

  • set foo abcdefghijklmnopqrstuvwxyzabcdeffasdffsdaadsx

这三种情形下 Redis 内部到底是怎么存数据的!

INT 编码格式

命令示例: setfoo123

当字符串键值的内容可以用一个 64位有符号整形 来表示时,Redis会将键值转化为 long型来进行存储,此时即对应 OBJ_ENCODING_INT 编码类型。

OBJ_ENCODING_INT 编码类型内部的内存结构可以形象地表示如下:

而且 Redis 启动时会预先建立 10000 个分别存储 0~9999 的 redisObject 变量作为共享对象,这就意味着如果 set字符串的键值在 0~10000 之间的话,则可以 直接指向共享对象 而不需要再建立新对象,此时键值不占空间!

因此,当执行如下指令时:

set key1 100set key2 100

其实 key1 和 key2 这两个键值都直接引用了一个 Redis 预先已建立好的共享 redisObject 对象,就像下面这样:

源码之前,了无秘密,我们再对照下面的源码,来理解一下上述过程

EMBSTR 编码格式

命令示例: setfoo abc

Redis 在保存长度小于 44 字节的字符串时会采用 OBJ_ENCODING_EMBSTR编码方式,口说无凭,我们来瞅瞅源码:

从上述代码中很容易看出,对于长度小于 44的字符串,Redis 对键值采用 OBJ_ENCODING_EMBSTR 方式,EMBSTR 顾名思义即:embedded string,表示嵌入式的String。从内存结构上来讲 即字符串 sds结构体与其对应的 redisObject 对象分配在 同一块连续的内存空间,这就仿佛字符串 sds 嵌入在 redisObject 对象之中一样,这一切从下面的代码即可清楚地看到:

因此,对于指令 setfoo abc 所设置的键值,其内存结构示意图如下:

RAW 编码格式

指令示例: setfoo abcdefghijklmnopqrstuvwxyzabcdeffasdffsdaadsx

正如指令示例,当字符串的键值为长度大于 44 的 超长字符串 时,Redis 则会将键值的内部编码方式改为 OBJ_ENCODING_RAW 格式,这与上面的 OBJ_ENCODING_EMBSTR 编码方式的不同之处在于 此时动态字符串 sds 的内存与其依赖的 redisObject 的 内存不再连续 了,以上述命令为例,其键值的内存结构如下所示:

到此就讲完了最基本的String数据类型的内部编码情况,怎么样,还是挺好理解的吧!

后续我们将继续剖析 Redis 中 Hash 数据类型的内部编码格式。

后 记

由于能力有限,若有错误或者不当之处,还请大家批评指正,一起学习交流!
  • 我的个人博客:www.codesheep.cn

如果有兴趣,也可以抽时间看看作者关于容器化、微服务化方面的文章:

  • 利用K8S技术栈打造个人私有云系列连载文章

  • Spring Boot Admin 2.0 上手体验

  • 利用TICK搭建Docker容器可视化监控中心

  • 从一份配置清单详解Nginx服务器配置

  • Docker容器可视化监控中心搭建

  • 利用ELK搭建Docker容器化应用日志中心

  • 高效编写Dockerfile的几条准则

  • Docker容器跨主机通信

  • Spring Boot应用监控实战

  • RPC框架实践之:Google gRPC

  • RPC框架实践之:Apache Thrift

  • 微服务调用链追踪中心搭建

作者更多 务实、能看懂、可复现的 原创文章尽在公众号 CodeSheep,欢迎订阅 ⬇️⬇️⬇️

©著作权归作者所有:来自51CTO博客作者mb5fdcae83766b7的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. 学习C第二天-字符串
  2. 从Excel的数据类型说Python
  3. Python进阶:如何将字符串常量转为变量?
  4. Python是否支持复制字符串呢?
  5. 你真的知道Python的字符串怎么用吗?
  6. 从简单到复杂,一文带你搞懂滑动窗口在数组及字符串中的应用
  7. 你真的知道Python的字符串是什么吗?
  8. 详解Python拼接字符串的七种方式
  9. Python字符串必备速查表.pdf

随机推荐

  1. SignalR Self Host多端消息推送服务介绍(
  2. ASP.NET Core环境设置教程(2)_实用技巧
  3. 详解关于Blog实现一个日历的实例教程
  4. ASP.NET中的参数与特殊类型的实例详解
  5. ASP.NET Core新建项目教程(3)_实用技巧
  6. C/C++中用空指针简化代码实例
  7. ASP.NET Core项目结构教程(4)_实用技巧
  8. ASP.NET中healthMonitor属性的用法教程
  9. ASP.NET Core Project.json文件(5)_实用技
  10. 关于继承和多态性的实例代码