深度分析Redis的二进制安全

简单动态字符串(Simple Dynamic Strings,SDS)是Redis的基本数据结构之一,用于存储字符串和整型数据。SDS兼容C语言标准字符串处理函数,且在此基础上保证了二进制安全。
什么是二进制安全?
通俗的讲,C语言中,用“0”表示字符串的结束,如果字符串中本身就有“0”字符,那么这个字符串就会被截断,即非二进制安全;若通过某种机制,保证读写字符串时不损害其内容,则是二进制安全。
Redis 3.2 之前的SDS主要是通过int len; int free; char buf[];这三个字段来确定一个字符串的。其中len表示buf中已占用字节数,free表示buf中剩余可用字节数,buf是数据空间。
这样设计有什么优点?
1、有单独的统计变量len和free(称为头部),可以很方便的得到字符串的长度。
2、内容存放在柔性数组buf中,SDS对上层暴露的指针不是指向结构体SDS的指针,而是直接指向柔性数组buf的指针。上层可以像读取C字符串一样读取SDS的内容,兼容C语言处理字符串的各种函数。
3、由于有长度的统计变量len的存在,读写字符串时不依赖“0”终止符,保证了二进制安全。
为什么要用柔性数组?
柔性数组的地址和结构体是连续的,这样查找内存更快(因为不需要额外通过指针找到字符串的位置);可以更快的通过柔性数组的首地址偏移得到结构体首地址,进而能很方便的获取其余变量。
思考:这样的设计有个缺点,不同长度的字符串需要占用相同大小的头部,显然是浪费了空间。
那么在之后的版本中Redis 5.0 做了什么样的改进呢?
深度分析Redis的二进制安全_第1张图片
这4种结构的成员变量类似,唯一的区别是len和alloc的类型不同。结构体中4个字段的具体含义如下:
1、len表示buf中已占用字节数。
2、alloc表示buf中已分配字节数,记录的是为buf分配的总长度,不同于free。
3、flags标识当前结构体的类型,低3位用作标识位,高5位预留。
4、buf柔性数组,真正存储字符串的数据空间。
创建SDS的大致流程是,首先计算好不同类型的头部和初始长度,然后动态分配内存。不过,需要注意一下3点:
1、创建空字符串是SDS_TYPE_5会被强制转换为SDS_TYPE_8。
2、长度计算时有“+1”操作,是为了算上结束符“0”。
3、返回值是指向sds结构buf字段的指针。

常见面试题:
1、SDS如何兼容C语言字符串?如何保证二进制安全?
SDS对象中的buf是一个柔性数组,上层调用时,SDS直接返回了buf。由于buf是直接指向内容的指针,所以兼容C语言函数。而当真正读取内容时,SDS会通过len来限制读取长度,而非“0”,所以保证了二进制安全。
2、sdshdr5的特殊之处是什么?
sdshdr5只负责存储小于32字节的字符串。一般情况下,小字符串的存储更普遍,所以Redis进一步压缩了sdshdr5的数据结构,将sdshdr5的类型和长度放入了同一个属性中,用flags的低3位存储类型,高5位存储长度。创建空字符串时,sdshdr5会被sdshdr8替代。
3、SDS是如何扩容的?
SDS在涉及字符串修改时会调用sdsMakeroomFor函数进行检查,会根据空闲长度和新增内容的长度进行比较判断,然后根据不同情况动态扩容,该操作对上层透明。(具体扩容步骤比较复杂,此处暂不展开讨论,有兴趣的同学可自行研究)
更多内容,请关注微信公众号:架构视角

你可能感兴趣的:(redis)