Redis及SDS初识
- 1.Redis简介
- 1.1 Redis优点
- 1.2 与Memcached对比
- 2.简单动态字符串-SDS
- 2.1 SDS简介
- 2.2 SDS数据结构
- 2.3 SDS相对于char*的优点
- (1)快速获取长度和剩余空间
- (2)二进制安全
- (3)防止越界
- (4)空间预分配和惰性释放
- (4.1)空间预分配
- (4.2)惰性空间释放
- (5)兼容C语言处理字符串的函数
- 2.4 SDS优化
- 2.4.1 SDS多容量类型优化(头部优化)
- (1)5种类型
- sdshdr5
- sdshdr16
- (2)结构体对齐
- 2.4.2 SDS扩容
1.Redis简介
Redis(REmote DIctionary Server)是使用C语言编写的、开源的、支持网络的、基于内存的、可选持久化的键值对数据库。
2009年第一版,2018年redis 5.0.0,2019年redis 6,目前最新。
1.1 Redis优点
- 内存型数据库,读写速度快
- 单线程(一个线程处理网络请求,IO多路复用),避免了线程之间的切换,不存在加锁、解锁等同步操作
- 支持复杂的数据类型,key value的value可以是多种类型:string list set zset hash
- 支持数据持久化:RDB AOF RDB&AOF 三种方案,重启后数据可回复
- 支持主从结构,集群模式
1.2 与Memcached对比
- 内存型数据库
- 多线程
- 只支持string、二进制类型
- 不支持数据持久化,重启后数据丢失
- 不支持集群模式,需要依赖客户端实现在集群中分片写入数据
2.简单动态字符串-SDS
2.1 SDS简介
简单动态字符串(Simple Dynamic Strings,SDS)是Redis的基本数据结构之一,也是Redis高效工作的武器之一。SDS用于存储字符串和整形数据,并且保证了二进制安全(后续会说明)。
2.2 SDS数据结构
struct sds {
int len; //buf中已占用字节数,即当前长度
int free; //buf中剩余可用字节数
char buf[];//柔性数组,可伸缩(可扩容)
}
2.3 SDS相对于char*的优点
(1)快速获取长度和剩余空间
根据统计变量len,free可以更快的得到字符串的长度和可用字节数,O(1)
(2)二进制安全
C语言中,"\0"表示字符串的结束,如果字符串中本身就有"\0",字符串会被截断,该问题称为非二进制安全。SDS中可以根据len可知字符串是否终止,不必依赖于"\0"。
(3)防止越界
当对SDS进行修改、合并、追加等操作时,可以根据len free等进行边界检查(还有自动扩容及类型转换),防止操作越界。
(4)空间预分配和惰性释放
(4.1)空间预分配
用于SDS字符串的增长:当对SDS进行拼接修改时,会进行边界检查,如有需要,会自动进行空间扩展,为其分期所必须的空间以及额外的未使用空间(sdsMakeRoomFor)。
(4.2)惰性空间释放
用于SDS字符串的缩短(释放):并非使用内存重分配释放多出来的内存,而是通过重置len free记录,
新的数据可以覆盖写,不用重新申请内存。
当然,SDS也提供了相应的API,使其在需要时真正地释放未使用空间,避免惰性空间释放导致的空间浪费。
(5)兼容C语言处理字符串的函数
SDS对上层暴露的指针不是SDS结构体的指针,而是指向柔性数组buf的指针。可直接对其使用C语言的字符串处理函数。
2.4 SDS优化
2.4.1 SDS多容量类型优化(头部优化)
上述SDS结构,对于短字符串来说,内容可能只有1字节,但头部却占了4个字节,浪费空间
对于很长的字符串来说,len和free都是4个字节,能存储的字符数量最多也只能为232
(1)5种类型
分为5种类型(头部长度小于1字节、1字节、2字节、4字节、8字节),使用1字节的标志字段flags来标识类型(sdshdr5种flags也用于记录buf长度)。sdshdr5 sdshdr8 sdshdr16 sdshdr32 sdshdr64
sdshdr5
struct __atribute__((__packed)) sdshdr5{
unsigned char flags; //低3位表示类型,高5位表示长度
char buf[];
}
sdshdr5种flags的低3位表示类型(23=8),高5位表示长度(25=32 ,即可用于表示长度小于32的短字符串)
sdshdr16
sdshdr8(最长28) sdshdr16(最长216) sdshdr32(最长232=4GB) sdshdr64(最长264=224T) 结构相同。
struct __atribute__((__packed)) sdshdr8{
unit8_t len; //已使用长度
unit8_t alloc; //总长度,1字节
unsigned char flags; //低3位表示类型,高5位预留
char buf[];
}
struct __atribute__((__packed)) sdshdr16{
unit16_t len; //已使用长度
unit16_t alloc; //总长度,2字节
unsigned char flags; //低3位表示类型,高5位预留
char buf[];
}
...
这里以sdshdr16为例:
(2)结构体对齐
__atribute__((__packed__))修饰,使结构体由按照变量大小的最小公倍数(sdshdr16是2,sdshdr32是24)字节对齐变为按1字节对齐,一下以sdshdr32为例:
改为1字节对齐后:
- 节省了3字节
- 能使用buf[-1]找到flags。否则还要对不能的结构进行处理buf[-4] buf[-8]…等,更复杂。
2.4.2 SDS扩容
当SDS字符串进行拼接操作时,可能会出现buf柔性数组的长度不能够容纳拼接后的字符串,因此再SDS拼接操作中,首先进行扩容检查(sdsMakeRoomFor),根据情况确定是否扩容。流程如下:
- 检查buf数组种剩余空间avail是否大于需要添加的长度addlen:
是:无需扩容,直接返回
否:需要扩容,转至2 - 检查拼接后长度是否大于1MB:
是:扩容长度=1MB+拼接后长度
否:扩容长度=2*(拼接后长度) - 根据新长度重新选取存储类型,分配空间,统计值赋值
新长度类型不变:realloc申请内存空间(扩大buf数组)
改变:重新开辟内存空间,将原buf的内容移动至新空间
本文参考《Redis5 设计与源码分析》