Redis 中 EMBSTR 对象的阈值设置为何为 44?其调整历史是什么?

Sherwin.Wei Lv7

Redis 中 EMBSTR 对象的阈值设置为何为 44?其调整历史是什么?

为什么 EMBSTR 的阈值大小是 44 个字节?

这个问题有几个关键点:

1)Redis 使用的是 jemalloc 作为内存分配器。

2)jemalloc 是以 64 字节作为内存单位进行内存分配的,如果超过了 64 字节,即超过了一个内存单元,使用的就是 raw 编码,反之使用的就是 EMBSTR 编码。

3)核心就是这个 64 字节,围绕 64 字节这个关键点来分析。Redis 的字符串对象是 redisObject 和 sdshdr 这两个部分组成的,redisObject 大小为

4 + 4 + 24 + 32 + 64 = 128bits = 16 bytes(16 字节)

这个是一直没有改变的,其计算的来源如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
   // from Redis 3.9.5​
#define LRU_BITS 24​

typedef struct redisObject {
unsigned type:4;​
unsigned encoding:4;​
/* LRU time or LFU data */
unsigned lru:LRU_BITS;

int refcount;​
void *ptr;​
} robj;

然后我们再来看一下 sdshdr 的结构:

1
2
3
4
5
6
7
// from Redis 3.9.5​
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[];​
};

sdshdr 占用的内存大小:3 byte + 字符数组的大小,由于字符数组内部保留的一个’\0’的占位符,所以剩下能用的空间就只有 44 个字节了。

那为什么之前版本的阈值是 39 呢?

其主要还是因为 sds 结构的版本差异,在 3.2 以前 sdshdr 的版本结构如下:

1
2
3
4
5
struct SDS {
unsigned int capacity; // 4byte​
unsigned int len; // 4byte​
byte[] content; // 内联数组,长度为 capacity​
}

我们可以看到,非数据字段就占用了 8 个字节,为了节约内存,3.2 版本之后的sds不再使用 sdshdr5 这个结构了,就剩下 sdshdr8、sdshdr16、sdshdr32、sdshdr64 这 4 个结构。

然后 EMBSTR 使用 sdsjdr8 节约了 6 个字节,然后多引入一个 flags 字段占用 1 字节,所以现版本的 EMBSTR 相比 3.2 版本之前的 sds 多了 5 个字节。

总结

主要是因为 Redis 使用 jemalloc 内存分配器,jemalloc 以 64 字节作为阈值区分大小字符串 raw 和 EMBSTR。

然后 redisObject 固定占用 16 个字节,然后 sdshdr 中已分配、已申请、标记这 3 个字段各自占用 1 个字节,’\0’占用 1 个字节,最终剩余 44 个字节。

因为 3.2 前后 Redis 关于 sdshdr 结构的差异,3.2 之后的版本 EMBSTR 使用 sdshdr8 这个结构,总容量和已使用容量字段减少了 6 个字节,但是 3.2 之后的版本增加了一个 flags 字段,所以最终 3.2 版本之前的 EMBSTR 结构少了 5 个字节。

Comments