浅析 Redis 中 String 数据类型及其底层编码( 二 )


动态字符串(SDS)动态字符串的结构体如下

浅析 Redis 中 String 数据类型及其底层编码

文章插图
这里解释一下结构体中各个成员变量的作用:
  • len:已经保存的字符串字节数 , 不包含结束标示
  • alloc:申请的总的字节数 , 不包含结束标示
  • flags:不同的 SDS 的头类型 , 用来控制 SDS 的头大小
  • buf[]:真正存储数据
我们先来聊一下 flags 这个成员变量 。在 redis 中其实定义了 5 个 SDS结构体(其中 hisdshdr5 已经弃用)如图所示 。他们之间的主要区别在于 len 和 alloc 的长度不同 。
在 redis 中 , 为了尽可能地节省内存空间 , 当字符串长度在不同的区间时 , 会选择不同的结构体 , 例如:
  • 当字符串长度在 0~255 个字节之间时 , 会选择 hisdshdr8  , 这样一来 , 用于表示字符串字节数和申请的总字节数的空间就会被大大节省 , 以此类推 。

浅析 Redis 中 String 数据类型及其底层编码

文章插图
例如 , 一个包含字符串“name”的 sds 结构如下:
浅析 Redis 中 String 数据类型及其底层编码

文章插图
SDS之所以叫做动态字符串 , 是因为它具备动态扩容的能力 , 例如一个内容为 “hello” 的 SDS , 假如我们要给这个 SDS 追加一段字符串 ”world”  , 这里首先会申请新内存空间:
  • 如果新字符串小于1M , 则新空间为扩展后字符串长度的两倍+1
  • 如果新字符串大于1M , 则新空间为扩展后字符串长度+1M+1 。
这种机制称为内存预分配 。内存预分配可以减少进行内存重新分配的开销 , 减少内存碎片 , 使得 redis 的性能得到提高 , 空间利用率也得到提高 。
String 的三种编码方式RAW
  • raw 是 string 的基本编码方式 , 基于简单动态字符串(SDS)实现 , 存储上限为512mb 。当一个字符串采用 raw 的编码方式的时候 , 它的结构如图所示 。

浅析 Redis 中 String 数据类型及其底层编码

文章插图
EMBSTR
  • 如果存储在 SDS 中的数据小于等于 44 字节 , 则会采用 EMBSTR 编码 , 此时 **RedisObject 与 SDS 是一段连续空间 。而不是像 RAW 的编码方式一样 , 由 ptr 指向另外一片空间 , **申请内存时只需要调用一次内存分配函数 , 效率更高 。结构如下 , 
为什么是 44 字节?Redis 默认的内存分配器 jemalloc 分配内存大小的单位是 $2^n$  , 因此 , 如果分配的空间大小为 2、4 、8 … 字节等 $2^n$ 字节 , 就不会产生内存碎片 。
而 redisObject 和 hisdshdr8 中 len alloc flags三个成员变量加起来刚刚好是 16 + 4 = 20 字节 , 如果 char[] (数据大小)的大小为 44 字节时 , 加起来刚刚好是 64 字节 , 也即 26 不会产生内存碎片 。
  • RAW 和 EMBSTR 的编码演示

浅析 Redis 中 String 数据类型及其底层编码

文章插图
INT
  • 如果存储的字符串是整数值 , 并且大小在 LONG MAX 范围内 , 则会采用 INT 编码
  • 直接将数据保存在 RedisObject 的 ptr 指针位置(刚好8字节) , 不再需要SDS了 。

浅析 Redis 中 String 数据类型及其底层编码

文章插图
  • INT 编码演示

浅析 Redis 中 String 数据类型及其底层编码

文章插图
写在最后:在使用 string 类型时 , 尽可能让其长度小于 44 字节 , 或者使用整数表示 , 使其使用 EMBSTR 和 INT 编码




推荐阅读