乐趣区

关于redis:闲扯Redis二String数据类型之底层解析


一、前言

Redis 提供了 5 种数据类型:String(字符串)、Hash(哈希)、List(列表)、Set(汇合)、Zset(有序汇合),了解每种数据类型的特点对于 redis 的开发和运维十分重要。

二、疑难与解析

结构图上显示,String 类型有三种实现形式

  • 应用整数值实现的字符串对象
  • 应用 embstr 编码的动静字符串实现的字符串对象
  • 动静字符串实现的字符串对象

疑难:<font color=’red’>embstr</font> 是什么意思,<font color=’red’> 动静字符串 </font> 又是什么意思?<font color=’red’> 字符串对象 </font> 到底什么构造?<font color=’red’> 三种实现形式 </font> 有什么区别呢?<center><p></p></center>

不急,咱们一步一步的往下看:

1、Redis 中定义的对象的构造体

/*
 * Redis 对象
 */
typedef struct redisObject {
    // 类型 4bits
    unsigned type:4;
    // 编码方式 4bits
    unsigned encoding:4;
    // LRU 工夫(绝对于 server.lruclock)24bits
    unsigned lru:22;
    // 援用计数 Redis 外面的数据能够通过援用计数进行共享 32bits
    int refcount;
    // 指向对象的值 64-bit
    void *ptr;
} robj;// 16bytes

正文:type 示意该对象的类型,即下面 [String,List,Hash,Set,Zset] 中的一个,但为了进步存储效率与程序执行效率,每种对象的底层数据结构实现都可能不止一种,encoding 示意对象底层所应用的编码。

2、Redis 对象底层八种数据结构

 REDIS_ENCODING_INT(long 类型的整数)REDIS_ENCODING_EMBSTR embstr(编码的简略动静字符串)REDIS_ENCODING_RAW(简略动静字符串)REDIS_ENCODING_HT(字典)REDIS_ENCODING_LINKEDLIST(双端链表)REDIS_ENCODING_ZIPLIST(压缩列表)REDIS_ENCODING_INTSET(整数汇合)REDIS_ENCODING_SKIPLIST(跳跃表和字典)

3、embstr 与动静字符串

<font color=’red’>embstr</font>:是专门用于保留短字符串的一种优化编码方式,跟失常的字符编码相比,字符编码会调用两次内存调配函数来别离创立 redisObject 和 sdshdr 构造(<font color=’red’> 动静字符串构造 </font>),而 embstr 编码则通过调用一次内存调配函数来调配一块间断的内存空间,空间中蕴含 redisObject 和 sdshdr(动静字符串)两个构造,两者在同一个内存块中。从 Redis 3.0 版本开始,字符串引入了 embstr 编码方式,长度小于 OBJ_ENCODING_EMBSTR_SIZE_LIMIT(39) 的字符串将以 EMBSTR 形式存储。

留神:在 Redis 3.2 之后,就不是以 39 为分界线,而是以 44 为分界线,次要与 Redis 中内存调配应用的是 jemalloc 无关。(jemalloc 分配内存的时候是依照 8、16、32、64 作为 chunk 的单位进行调配的。为了保障采纳这种编码方式的字符串能被 jemalloc 调配在同一个 chunk 中,该字符串长度不能超过 64,故字符串长度限度

OBJ_ENCODING_EMBSTR_SIZE_LIMIT = 64 – sizeof(‘0’)为 1 – sizeof(robj) 为 16 – sizeof(struct sdshdr)为 8 = 39)

<font color=’red’> 动静字符串 </font>:Redis 本人构建的一种名为 简略动静字符串(simple dynamic string,SDS)的形象类型,并将 SDS 作为 Redis 的默认字符串示意。先简略理解概念,前面看具体解析

4、带着疑难来细品上面一段话

字符串的编码能够是 int,raw 或者 embstr。如果一个字符串内容可转为 long,那么该字符串会被转化为 long 类型,对象 ptr 指向该 long,并且对象类型也用 int 类型示意。一般的字符串有两种 embstr 和 raw。如果字符串对象的长度小于 39 字节,就用 embstr,否则用 raw。

也就是说,Redis 会依据以后值的类型和长度决定应用外部编码实现:豁然开朗

int:8 个字节的长整型
embstr:小于等于 39 个字节的字符串
raw:大于 39 个字节的字符串

5、实际验证

命令:object encoding key,获取数据底层的数据结构

1)整数类型示例如下:

2)短字符串示例如下:

3)长字符串示例如下:

疑难 :至此,咱们晓得了 <font color=’red’>embstr、字符串对象 </font>,然而动静字符串的构造还是没说分明啊, 你是不是在逗我?
靓仔疑难,再一次呈现,别急,持续往下看 <center></center>

三、动静字符串

家喻户晓,Redis 是用 C 语言写的,然而对于 Redis 的字符串,却不是 C 语言中的字符串(即以空字符’0’结尾的字符数组),它是本人构建了一种名为 简略动静字符串(simple dynamic string,SDS)的形象类型,并将 SDS 作为 Redis 的默认字符串示意。

1、动静字符串构造剖析

SDS 定义

struct sdshdr{
     // 记录 buf 数组中已应用字节的数量
     // 等于 SDS 保留字符串的长度 4byte
     int len;
     // 记录 buf 数组中未应用字节的数量 4byte
     int free;
     // 字节数组,用于保留字符串 字节 \0 结尾的字符串占用了 1byte
     char buf[];}

用 SDS 保留字符串“Redis”具体构造如下图 <center></center>
对于 SDS 数据类型的定义:

  • len 保留了 SDS 保留字符串的长度
  • buf[] 数组用来保留字符串的每个元素
  • free 记录了 buf 数组中未应用的字节数量

下面的定义绝对于 C 语言对于字符串的定义,多出了 len 属性以及 free 属性。为什么不间接应用 C 语言字符串实现,而是要应用 SDS 呢?有什么特地的劣势呢?

2、SDS 构造与 C 语言字符串构造比拟剖析 <center></center>

1)获取字符串长度复杂度

 sdshdr 中因为 len 属性的存在,获取 SDS 字符串的长度只须要读取 len 属性,工夫复杂度为 O(1),而对于 C 语言来说,获取字符串的长度通常是遍历字符串计数来实现的,工夫复杂度为 O(n)。

2)API 安全性与缓冲区溢出

  缓冲区溢出(buffer overflow):是这样的一种异样,当程序将数据写入缓冲区时,会超过缓冲区的边界,并笼罩相邻的内存地位。在 C 语言中应用 strcat 函数来进行两个字符串的拼接,一旦没有调配足够长度的内存空间,就会造成缓冲区溢出,如 <center></center>

s1 = ‘Redis’,s2 = ‘MongoDB’,当执行 strcat(s1, ” Cluster”)时,未给 s1 调配足够内存空间,s1 的数据将溢出到 s2 所在的内存空间,导致 s2 保留的内容被意外地批改。<center></center>

因为 SDS 记录了本身长度,同时在批改时,API 会依照如下步骤进行:

(1)先查看 SDS 的空间是否满足批改所需的要求;(2)如果不满足要求的话,API 会主动将 SDS 的空间扩大至执行批改所需的大小(realloc);(3)而后才执行理论的批改操作;所以 SDS 不会造成缓冲区溢出状况

3)字符串的内存重调配次数

 C 语言因为不记录字符串的长度,所以如果要批改字符串,必须要从新分配内存。
 SDS 实现了空间预调配和惰性开释两种策略:

(1)空间预调配:当 SDS 的 API 对一个 SDS 进行批改,并且须要对 SDS 进行空间扩大的时候,程序不仅会为 SDS 调配批改所必须的空间,还会为 SDS 调配额定的未应用空间,这样能够缩小间断执行字符串增长操作所需的内存重调配次数。(2)惰性开释:当 SDS 的 API 须要对 SDS 保留的字符串进行缩短时,程序并不立刻应用内存重调配来回收缩短后多进去的字节,而是应用 free 属性将这些字节的数量记录起来,并期待未来应用,如 <center>![](https://img2020.cnblogs.com/blog/675297/202003/675297-20200328110346123-720902804.png)</center>
sdstrim(s, "XY"); // 移除 SDS 字符串中的所有 'X' 和 'Y' 

后果 <center></center>

4)二进制数据安全

 二进制平安(binary-safe):指能解决任意的二进制数据,包含非 ASCII 和 null 字节。
 C 字符串以空字符 ‘0’,作为字符串完结的标识,而对于一些二进制文件(如图片等),内容可能包含空字符串 ’0’,导致程序读入的空字符会被误认为是字符串的结尾,因而 C 字符串无奈正确存取二进制数据;
 SDS 的 API 都是以解决二进制的形式来解决 buf 外面的元素,并且 SDS 不是以空字符串 ’0’ 来判断是否完结,而是以 len 属性示意的长度来判断字符串是否完结,

因而 <font color=’red’> Redis </font> 不仅能够保留文本数据,还能够保留任意格局的二进制数据。

5)C 字符串函数兼容

  SDS 的 buf 数组会以 ’0’ 结尾,这样能够重用 C 语言库 <string.h> 中的一部分函数,防止了不必要的代码反复。

四、要点总结

String 类型对象三种实现形式,int,embstr,raw
字符串内容可转为 long,采纳 int 类型,否则长度 <39(3.2 版本前 39,3.2 版本后分界线 44)用 embstr,其余用 raw
SDS 是 Redis 本人构建的一种简略动静字符串的形象类型,并将 SDS 作为 Redis 的默认字符串示意
SDS 与 C 语言字符串构造相比,具备五大劣势

退出移动版