Redis字符串类型内部编码剖析

Stella981
• 阅读 390

Redis字符串类型内部编码剖析


概述

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

  • 实验环境:Redis 4.0.10

注: 本文首发于 My Personal Blog,欢迎光临 小站



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 bar
OK
127.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语言结构体保存的,其代码如下:

Redis字符串类型内部编码剖析

解释如下:

  • 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 中字符串对象的编码可以是 intraw 或者 embstr 中的某一种,分别介绍如下:

  • int 编码:保存long 型的64位有符号整数
  • embstr 编码:保存长度小于44字节的字符串
  • raw 编码:保存长度大于44字节的字符串

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

Redis字符串类型内部编码剖析

实际情况就是 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 编码格式

命令示例: set foo 123

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

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

Redis字符串类型内部编码剖析

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

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

set key1 100
set key2 100

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

Redis字符串类型内部编码剖析

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

Redis字符串类型内部编码剖析



EMBSTR编码格式

命令示例: set foo abc

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

Redis字符串类型内部编码剖析

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

Redis字符串类型内部编码剖析

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

Redis字符串类型内部编码剖析



RAW 编码格式

指令示例: set foo abcdefghijklmnopqrstuvwxyzabcdeffasdffsdaadsx

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

Redis字符串类型内部编码剖析

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

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



后 记

由于能力有限,若有错误或者不当之处,还请大家批评指正,一起学习交流!

作者更多的SpringBt实践文章在此:


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


长按扫描 下面的 小心心 来订阅 CodeSheep,获取更多 务实、能看懂、可复现的 原创文 ↓↓↓

Redis字符串类型内部编码剖析


点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
2年前
Eureka Server 开启Spring Security Basic认证
!Desktop(https://uploadimages.jianshu.io/upload_images/98242475ce94f98ae00f42f.jpg?imageMogr2/autoorient/strip%7CimageView2/2/w/1240)文章共503字,阅读大约需要2分钟!概述
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
4个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这