Redis 设计与实现 6:五大数据类型之字符串

前文 Redis 设计与实现 2:Redis 对象 说到,五大数据类型都会封装成 RedisObject

typedef struct redisObject {
    unsigned type:4; // 类型
    unsigned encoding:4; // 编码
    // ...
    void *ptr; // 指向具体底层数据的指针
} robj;

不同数据类型的主要区别就是 typeencoding 属性的差异,同一种数据类型,有不同的编码。

一、编码类型

字符串的编码有rawembstrint三种。

  • raw 用于长字符串。
  • embstr 用于短字符串。
  • int 用于整数类型。

定义在 server.h 中,这里只列出 string 类型的编码

#define OBJ_ENCODING_RAW 0
#define OBJ_ENCODING_INT 1
#define OBJ_ENCODING_EMBSTR 8

编码 1:raw

raw 编码主要用来保存长度超过 44 的字符串。其真实数据,由 sdshdr 结构来表示存储,外层还是由 redisObject 包装。
sdshdr 的结构在前文 Redis 设计与实现 3:字符串 SDS 中有讲到。
sdshdr 结构大致如下:

sdshdr 结构

redisObject 中的 ptr 指针,就是指向 sds

string raw 编码结构示例

编码 2:embstr

embstr 编码是专门用于保存短字符串的一种优化编码方式。当字符串的长度小于等于 44 的时候,将采用 embstr 编码。

创建字符串对象的代码如下(object.c):

#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
        return createEmbeddedStringObject(ptr,len);
    else
        return createRawStringObject(ptr,len);
}

embstr 有个显著的特点,就是 redisObjectsds 的内存是挨在一起的。挨在一起的好处:

  • 分配内存的时候,只需要分配一次。而 raw 编码的sdsredisObject分离,就要分配两次内存。
  • 同样,释放内存也只需要释放一次。
  • 连续内存能更好利用内存带来的优势。

embstr 问题一:那么为什么 embstr 跟 raw 的界限是 44 呢?

  • embstrsds使用了 sdshdr8sdshdr8 头占用了 3 个字节:
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* 1 字节 */
    uint8_t alloc; /* 1 字节 */
    unsigned char flags; /* 1 字节 */
    char buf[];
};
  • 另外还有 redisObject 占用 16 个字节 (4 + 4 + 24 + 32 + 64 = 128 位):
typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS; // #define LRU_BITS 24
    int refcount; // 32 位
    void *ptr; // 64 位
} robj;

redisObject + sdshdr8 至少需要 3 + 16 = 19 字节。

redis 认为如果超过 64 字节就是大字符串,所以在 redisObject+ sdshdr8 的总长度是 64 字节的情况下,留给 buf 的长度就只剩下 45 字节,由于字符串结尾需要一个 \0 占用一个字节,所以留个字符串的长度就只有 44 字节了。

公式:64 - 3(sdshdr8 ) - 16(redisObject) - 1(\0) = 44

embstr 问题二:为什么网上有的博文说 embstr 跟 raw 的界限是 39

在 redis 3.2 版本之前,这个界限的确是 39,为什么后面改成 44 了呢?

那是因为 sdshdr 的结构在 3.2 版本的时候修改了。3.2 之前的 sdshdr 结构是:

struct sdshdr {
    unsigned int len; // 4 字节
    unsigned int free; // 4 字节
    char buf[];
};

旧版本的 sdshdr 的头占用了 8 个字节,比新版本的多了 5 个字节,所以界限就是 44 - 5 = 39 啦!

编码 3:int

如果一个字符串对象保存的是整数值,并且这个整数值可以用 long 类型来表示,那么这个整数值将会保存在字符串对象结构的 ptr 属性里面(将 void* 转换成 long),并将字符串对象的编码设置为 int

相对于用 raw 编码,int 编码既节省了指针占用的内存,也节省了sds结构的内存。

redis> SET int_key 12345
OK
redis> OBJECT ENCODING int_key
"int"

下图为存着 12345string 示例结构:

string int 编码结构示例

二、编码的转换

1. int 转 raw

  • 当字符串传的不是整数的时候,int 就会转成 raw 编码。
  • 如果执行了一些修改的命令,如 append 等( set 不算),都会转成 raw 编码。因为这些操作只有字符串才支持。
  • 一旦编码变为 raw 之后,将不会再转成 embstr
127.0.0.1:6379> SET num 1
OK
127.0.0.1:6379> OBJECT ENCODING num
"int"
127.0.0.1:6379> APPEND num 2
(integer) 2
127.0.0.1:6379> OBJECT ENCODING num
"raw"
127.0.0.1:6379> SET num 12
OK
127.0.0.1:6379> OBJECT ENCODING num
"int"

2. embstr 转 raw

  • 如果执行了一些修改的命令,如append等,都会转成 raw 编码,不管修改后字符串的长度。因为没有给 embstr 编码实现修改接口,所以实际上 embsr 是只读的。
  • 一旦编码变为 raw 之后,将不会再转成 embstr

三、重点回顾

  • 字符串对象有三种编码,rawembstrint
  • raw 负责保存长字符串; embstr 负责保存短字符串; int 负责保存整数。
  • intembstr 在修改的时候,会转成 raw 编码,并且不再转回

本文的分析没有特殊说明都是基于 Redis 6.0 版本源码
redis 6.0 源码:https://github.com/redis/redis/tree/6.0

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,755评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,305评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,138评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,791评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,794评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,631评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,362评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,264评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,724评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,900评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,040评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,742评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,364评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,944评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,060评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,247评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,979评论 2 355

推荐阅读更多精彩内容