咱们平时用 Redis都是处于用户层面,咱们可能会不加思索地操做一个 key-value 对来方便地存取数据,感受方便之至。但你知道这些数据在背后是如何存储以及编码的吗? 了解清楚了这个问题,将对咱们更加高效地使用 Redis具备指导意义。本文开始咱们将结合 Redis源码来逐个探讨Redis五大数据类型的内部编码机制。redis
注: 本文首发于 My 公众号 CodeSheep ,可 长按 或 扫描 下面的 当心心 来订阅 ↓ ↓ ↓编程
对于 Redis的经常使用 5 种数据类型(String、Hash、List、Set、sorted set),每种数据类型都提供了 最少两种 内部的编码格式,并且每一个数据类型内部编码方式的选择 对用户是彻底透明的,Redis会根据数据量自适应地选择较优化的内部编码格式。c#
若是想查看某个键的内部编码格式,可使用 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语言结构体保存的,其代码以下:bash
解释以下:服务器
type
:表示键值的数据类型,包括 String、List、Set、ZSet、Hashencoding
:表示键值的内部编码方式,从 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类型的内部编码开始探讨!数据结构
字符串是 Redis最基本的数据类型,Redis 中字符串对象的编码能够是 int
,raw
或者 embstr
中的某一种,分别介绍以下:框架
咱们不妨来作个实验实际看一下:微服务
实际状况就是 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
:字符数组了解了这些基本的数据结构之后,咱们就来看看上面例子中:
这三种情形下 Redis 内部究竟是怎么存数据的!
命令示例: set foo 123
当字符串键值的内容能够用一个 64位有符号整形 来表示时,Redis会将键值转化为 long型来进行存储,此时即对应 OBJ_ENCODING_INT
编码类型。
OBJ_ENCODING_INT
编码类型内部的内存结构能够形象地表示以下:
并且 Redis 启动时会预先创建 10000 个分别存储 0~9999 的 redisObject 变量做为共享对象,这就意味着若是 set字符串的键值在 0~10000 之间的话,则能够 直接指向共享对象 而不须要再创建新对象,此时键值不占空间!
所以,当执行以下指令时:
set key1 100
set key2 100
复制代码
其实 key1 和 key2 这两个键值都直接引用了一个 Redis 预先已创建好的共享 redisObject 对象,就像下面这样:
源码以前,了无秘密,咱们再对照下面的源码,来理解一下上述过程
命令示例: set foo abc
Redis 在保存长度小于 44 字节的字符串时会采用 OBJ_ENCODING_EMBSTR
编码方式,口说无凭,咱们来瞅瞅源码:
从上述代码中很容易看出,对于长度小于 44的字符串,Redis 对键值采用OBJ_ENCODING_EMBSTR
方式,EMBSTR 顾名思义即:embedded string,表示嵌入式的String。从内存结构上来说 即字符串 sds结构体与其对应的 redisObject 对象分配在 同一块连续的内存空间,这就仿佛字符串 sds 嵌入在 redisObject 对象之中同样,这一切从下面的代码便可清楚地看到:
所以,对于指令 set foo abc
所设置的键值,其内存结构示意图以下:
指令示例: set foo abcdefghijklmnopqrstuvwxyzabcdeffasdffsdaadsx
正如指令示例,当字符串的键值为长度大于 44 的 超长字符串 时,Redis 则会将键值的内部编码方式改成 OBJ_ENCODING_RAW
格式,这与上面的 OBJ_ENCODING_EMBSTR
编码方式的不一样之处在于 此时动态字符串 sds 的内存与其依赖的 redisObject 的 内存再也不连续 了,以 set foo abcdefghijklmnopqrstuvwxyzabcdeffasdffsdaadsx
为例,其键值的内存结构以下所示:
到此就讲完了最基本的String数据类型的内部编码状况,怎么样,仍是挺好理解的吧!
后续咱们将继续剖析 Redis 中 Hash 数据类型的内部编码格式。
因为能力有限,如有错误或者不当之处,还请你们批评指正,一块儿学习交流!
做者更多的SpringBt实践文章在此:
若是有兴趣,也能够抽点时间看看做者一些关于容器化、微服务化方面的文章:
可 长按 或 扫描 下面的 当心心 来订阅 CodeSheep,获取更多 务实、能看懂、可复现的 原创文 ↓↓↓