Redis 源码分析字符串对象(z_string)

1,428 阅读2分钟

「这是我参与2022首次更文挑战的第36天,活动详情查看:2022首次更文挑战」。

字符串对象

字符对象的三种编码可以是 int, raw 或者 embstr, 三种情况我分别来说明一下:

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

  2. 如果一个字符串保存的是一个字符值,并且这个字符串长度小于等于 32 字节,那么这个字符串讲使用 embstr 编码的形式来保存这个字符串值。

  3. embstr 编码是专门用来保存短字符串的一种优化编码方式这种编码和 raw 编码一样,都是使用 redisObject 中的 sdshdr 结构来表示字符串对象,但是 raw 会调用两次内存比分配函数来创建 redisObject 结构和 sdshdr 结构,而 embstr 编码则通过调用一次内存分配一款连续空间,空间依次包含 redisObject 和 sdshdr 两个结构

int 编码的字符串如下:

image.png

embstr 编码的字符如下: image.png

raw 编码的字符如下: image.png 下面是 redisObject 所有的类型:

char *strEncoding(int encoding) {
    switch(encoding) {
    case OBJ_ENCODING_RAW: return "raw";
    case OBJ_ENCODING_INT: return "int";
    case OBJ_ENCODING_HT: return "hashtable";
    case OBJ_ENCODING_QUICKLIST: return "quicklist";
    case OBJ_ENCODING_ZIPLIST: return "ziplist";
    case OBJ_ENCODING_LISTPACK: return "listpack";
    case OBJ_ENCODING_INTSET: return "intset";
    case OBJ_ENCODING_SKIPLIST: return "skiplist";
    case OBJ_ENCODING_EMBSTR: return "embstr";
    case OBJ_ENCODING_STREAM: return "stream";
    default: return "unknown";
    }
}

我们本处主要是一起来分析 OBJ_ENCODING_INT、OBJ_ENCODING_RAW、OBJ_ENCODING_EMBSTR 三种情况

int (OBJ_ENCODING_INT)

长度小于 20 确保在一个长整型的范围内,2^64次方恰好占用 20 位,且是数字字符串类型。

image-20220220150038733.png

其实这里就是可以涵盖了所有 long 类型的长整型。

embstr (OBJ_ENCODING_EMBSTR)

长度小于等于 44 个字符(包括浮点数)44 个字节是只要为了适应 jemalloc 分配的 64k arena

/* Create a string object with EMBSTR encoding if it is smaller than
 * OBJ_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is
 * used.
 *
 * The current limit of 44 is chosen so that the biggest string object
 * we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */
#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);
}

这块其实可以就是理解为一个短的字符串, 且非 20 位内的整形。

raw (OBJ_ENCODING_RAW)

长度大于 44 字节的字符串(包括浮点数), 简单总结就是其他的字符串,或者字符串浮点数。下面会做详细的对比。

raw 和 embstr 对比

raw 和 embstr 对比如下:

类型特点优点缺点
embstr1. 只分分配一次内存空间,因此 robj 和 sds 是连续的;2. 只读;3. Embstr 字符串需要修改时,会转换为 raw, 之后一直是 raw1. 创建和删除只需要一次;2.查找速度快1. 重新分配涉及到 robj 和 sds 整个对象,因此 embstr 是只读的
raw1. robj 和 sds 非连续;2. 可以修改

embstr 创建过程如下:

image-20220220151243639.png

redisObject 对象创建:

image-20220220151312371.png

使用 append 命令修改字符串会改变字符串底层的类型:

image-20220220151450630.png

通过这个实验,证明了两点:

  1. 浮点数 redis 用 embstr 存储。
  2. embstr 如果有 append 操作那么会转换为 raw 存储。

底层代码(embstr 转换 raw):

image-20220220151659611.png

Redis 字符串限定

Redis 字符串约定长度不能大于 512m, 通常开发过程中字符串很难达到这么长,除非是你想存 base64 之类的, 默认值已经很大。

// 字符串最大长度限制
static int checkStringLength(client *c, long long size) {
    if (!(c->flags & CLIENT_MASTER) && size > server.proto_max_bulk_len) {
        addReplyError(c,"string exceeds maximum allowed size (proto-max-bulk-len)");
        return C_ERR;
    }
    return C_OK;
}

这长度我们可以在 redis.config 中配置, 配置 key proto-max-bulk-len 默认值:512mb. 如下图所示:

image.png

字符串使用场景

  • 缓存功能:mysql 做数据持久化存储,redis 做少量热数据缓存;
  • 计数器:如点赞次数,视频播放次数;
  • 限流:见基于 redis 的分布式限流;

使用命令和其他实践可以翻阅我以前的文章,本文不在赘述:

常用操作

image.png

参考文档

  • 《Redis 设计与实现》黄健宏