做了一阵子的成本优化,一直想整理些文章发出来,由于比较懒且“怂”一直没弄,最近决定还是整理下,本文是该系列文章的第一篇。
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自身的容量。
版本 | 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 |
版本 | 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 |
版本 | 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 |
版本 | 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 |
版本 | 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 |
本部分只是阐述SDS是什么,所以用最简单的Redis 3版本进行阐述。
SDS全称是Simple Dynamic String(简单动态字符串),它对C语言中的字符串实现进行了扩展。
一图胜千言万语,SDS和C字符串相比多了一个前缀sdshdr结构体,它的定义:
struct sdshdr {
unsigned int len //sds使用长度(字符串长度);
unsigned int free //sds剩余长度;
char buf[] //柔性字符数组;
SDS好处如下:
(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使用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通常是不连续的,如下图所示:
(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 |
(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在执行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
如果是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直接存储数字
如果觉得我的文章对您有用,请点赞。您的支持将鼓励我继续创作!
赞3
添加新评论0 条评论