Redis成本优化-版本升级-1.SDS优化历史

ilikevc
发布于 2023-12-4 15:10
浏览
0收藏

做了一阵子的成本优化,一直想整理些文章发出来,由于比较懒且“怂”一直没弄,最近决定还是整理下,本文是该系列文章的第一篇。(相关目录见文末)

一、几个实验

Redis实验版本(撰写文本的最新版本):2.8.24、3.0.7、3.2.13、4.0.14、5.0.14、6.0.20、6.2.15、7.0.12,
容量MB:去掉了Redis自身的容量
1. 写入100万条string键值对:key、value均为小于32字节字符串(测试用16字节)

版本

2.8.24

3.0.7

3.2.13

4.0.14

5.0.14

6.0.20

6.2.15

7.0.12

容量MB

114.87

114.87

91.98

92.04

92.04

92.00

92.04

92.10

Redis成本优化-版本升级-1.SDS优化历史-鸿蒙开发者社区

2. 写入100万条string键值对:key、value均为小于39字节字符串(测试用36字节)

版本

2.8.24

3.0.7

3.2.13

4.0.14

5.0.14

6.0.20

6.2.15

7.0.12

容量MB

145.38

145.38

122.50

122.56

122.56

122.56

122.56

122.62

Redis成本优化-版本升级-1.SDS优化历史-鸿蒙开发者社区

3.写入100万条string键值对:key、value均为39到44字节之间的字符串(测试用42字节)

版本

2.8.24

3.0.7

3.2.13

4.0.14

5.0.14

6.0.20

6.2.15

7.0.12

容量MB

175.90

175.90

137.76

137.82

137.82

137.78

137.82

137.88

Redis成本优化-版本升级-1.SDS优化历史-鸿蒙开发者社区

4.写入100万条键值对:key、value均为大于44字节(测试用100字节)

版本

2.8.24

3.0.7

3.2.13

4.0.14

5.0.14

6.0.20

6.2.15

7.0.12

容量MB

267.45

267.42

259.83

259.89

259.89

259.89

259.89

259.95

Redis成本优化-版本升级-1.SDS优化历史-鸿蒙开发者社区

5.写入100万条键值对:key为字符串(100字节),value为数字(>10000的数字)

版本

2.8.24

3.0.7

3.2.13

4.0.14

5.0.14

6.0.20

6.2.15

7.0.12

容量MB

160.64

175.90

168.28

168.34

153.08

153.08

153.08

153.14

Redis成本优化-版本升级-1.SDS优化历史-鸿蒙开发者社区

初步结论:

  • Redis 3.2版本相比于之前版本,Redis容量有较大降幅。
  • Redis 5.0版本在value为数字的场景下,Redis容量有较大降幅。

二、什么是SDS

本部分只是阐述SDS是什么,所以用最简单的Redis 3版本进行阐述。

SDS全称是Simple Dynamic String(简单动态字符串),它对C语言中的字符串实现进行了扩展。

Redis成本优化-版本升级-1.SDS优化历史-鸿蒙开发者社区

一图胜千言万语,SDS和C字符串相比多了一个前缀sdshdr结构体,它的定义:

struct sdshdr {
    unsigned int len //sds使用长度(字符串长度);
    unsigned int free //sds剩余长度;
    char buf[] //柔性字符数组;
};

例如如下两图说明了上述参数的含义:

Redis成本优化-版本升级-1.SDS优化历史-鸿蒙开发者社区

SDS好处如下:

  • 1.计算长度简单:SDS计算字符串长度复杂度是o(1),C语言是O(n)
  • 2.二进制安全:SDS不以\0作为字符串结束标识(用free、len),即使字符串包含了\0这样特殊字符,它也是安全的。
  • 3.安全扩展:SDS定义了一系列函数保证字符串不会出现越界等情况。

关于SDS是什么网上有很多文章,这里就不再赘述了。

三、SDS的相关优化

1. embedded string(since 3.0)

(1)  一个例子

127.0.0.1:12307> set hello world
OK
127.0.0.1:12307> object encoding hello
"embstr"
127.0.0.1:12307> set bigstr helloaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
OK
127.0.0.1:12307> strlen bigstr
(integer) 41
127.0.0.1:12307> object encoding bigstr
"raw"

(2) 对于非数字的字符串

  • 当小于等于39字节(注意这里是Redis 3),Redis使用embstr来实现。
  • 当大于39字节(注意这里是Redis 3),Redis使用raw来实现。

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

两者的区别是redisObject和embstr是一块连续内存,redisObject和raw通常是不连续的,如下图所示:

Redis成本优化-版本升级-1.SDS优化历史-鸿蒙开发者社区

Redis成本优化-版本升级-1.SDS优化历史-鸿蒙开发者社区

(3) 优势:embstr申请或释放内存都是一次,raw需要两次,同时由于是连续内存,查询速度会更快。

(4) 注意:embstr并没有节省内存:

数据规模

2.8.24

3.0.7

写入100万条键值对:key、value均为16字节

114.87

114.87

写入100万条键值对:key、value均为36字节

145.38

145.38

2. sdshdr拆分(since 3.2)

(1) sdshdr重大优化:如果字符串比较小(例如就几个字节),但是len、free会占用8个字节,确实是有一些浪费。

Redis 3.0:

struct sdshdr {
    unsigned int len;
    unsigned int free;
    char buf[];
};

于是在Redis3.2做了如下优化:按照字符串长度的不同,记录字符串len和free的类型会做相应变化:char、uint8_t、uint16_t、uint32_t、uint64_t。

Redis 3.2+:

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[];
};

(2) 以sdshdr8为例子进行说明,它的结构如下,从图中可以看到为什么Redis 3的embstr界限是39字节、Redis 3.2的embstr界限是44字节(修复图中Redis 4为Redis 3.2+)

Redis成本优化-版本升级-1.SDS优化历史-鸿蒙开发者社区

3. 数字优化(since 5.0.6)

Redis在执行set相关命令(setnx、setex、setnx、append)会调用object.c的tryObjectEncoding确认下value是否可以转成对应的编码已节省空间,下面是Redis 4.0.14的代码

robj *tryObjectEncoding(robj *o) {
    long value;
    sds s = o->ptr;
    size_t len;
    //......一些验证.....
    len = sdslen(s);
    if (len <= 20 && string2l(s,len,&value)) {
        //针对shareobject
        if ((server.maxmemory == 0 ||
            !(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
            value >= 0 &&
            value < OBJ_SHARED_INTEGERS)
        {
            decrRefCount(o);
            incrRefCount(shared.integers[value]);
            return shared.integers[value];
        } 
        //这里是优化重点
        else {
            if (o->encoding == OBJ_ENCODING_RAW) sdsfree(o->ptr);
            o->encoding = OBJ_ENCODING_INT;
            o->ptr = (void*) value;
            return o;
        }
    }
    ...
}

Redis 5.0.6对齐做了如下优化:https://github.com/redis/redis/commit/2f8a0749

Redis成本优化-版本升级-1.SDS优化历史-鸿蒙开发者社区

如果是20个字节以内的long类型,如果是非raw类型,可以转成embstr并针对性的做优化:

robj *createStringObjectFromLongLongForValue(long long value) {
    if (value >= LONG_MIN && value <= LONG_MAX) {
        o = createObject(OBJ_STRING, NULL);
        o->encoding = OBJ_ENCODING_INT;
        o->ptr = (void*)((long)value);
    } else {
        o = createObject(OBJ_STRING,sdsfromlonglong(value));
    }
    return o;
}

如果是在longmin和longmax之间,会优化成如下:ptr直接存储数字

Redis成本优化-版本升级-1.SDS优化历史-鸿蒙开发者社区

四、结论

  1. Redis 3.0的embstr减少了一次内存分配,可以提升整体性能,但是并未为做成本上的优化。
  2. Redis 3.2的sdshdr拆分是sds最大的成本优化,因为sds是复杂数据结构以及网络处理等的基础数据结构。
  3. Redis 5.0.6关于数字的优化虽然只改动了几行代码,但是针对数字类型非常明显。

五、几个思考问题(欢迎来讨论)

  • 为什么embstr的界限是39或者44字节?
  • 第一节的实验五的数字为什么要大于10000?
  • Redis 7.0似乎容量上有小幅上涨?


文章转载自公众号:Redis开发运维实战

分类
标签
已于2023-12-4 15:10:37修改
收藏
回复
举报
回复
    相关推荐