【Redis数据对象与结构】string与其底层结构
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
【Redis数据对象与结构】string与其底层结构
【Redis数据对象与结构】系列的主线如下本文主要讲解string数据对象及其底层结构在redis中的实现。
redis中基本的数据对象有字符串类型(String)、列表类型(List)、字典类型(Hash)、集合类型(Set)、有序列表类型(SortedSet)在5.0后redis又增加了一个新的数据对象——流类型(Stream)不是steam。接下来分别介绍每个数据对象的使用、底层编码及基本命令。
redis中的数据对象结构如下我们现在只需要记住这个对象的大小为 16字节。
typedef struct redisObject {
unsigned type:4; // 对象的类型4bit
unsigned encoding:4; // 对象的编码4bit
unsigned lru:LRU_BITS; // 先不管24bit
int refcount; // 先不管32bit
void *ptr; // 对象的值的指针64bit
} robj;
对象的类型每个对象都有其类型使用type xxx
可获得xxx
的类型。类型为数据对象五大类型中的一种。
对象的编码对应类型的实现方式使用object encoding xxx
可获得xxx
的类型。也就是这个对象使用了什么数据结构作为对象的底层实现。
简单字符串(SDS)
redis中实现字符串类型的一种编码实现方式。redis中的SDS的结构如下
struct sdshdr {
//记录buf数组中已使用字节的数量
//等于SDS所保存字符串的长度
int len;
//记录buf数组中未使用字节的数量
int free;
//字节数组用于保存字符串
char buf[];
};
- SDS基于C语言的字符串保留了以
\0
为结尾的惯例这可以重用C字符串库里的一部分函数。但是真正读取字符串的值的时候是以len
为准的。 - SDS中对于末尾
\0
的添加对于len
的计算与更新对SDS的使用者来说是完全透明的。
SDS是二进制安全的吗为什么
看到有free
字段就如同go slice中的cap一样肯定是存在动态预分配的。策略都是类似的
- 当更新后的
len
小于1MB那么扩容一倍。 - 当更新后的
len
大于1MB那么扩容1MB。 - 如果更新后的存储类型改变了就直接开辟新的内存并将原字符串的内容移动到新位置。
顺便了解一下go slice的扩容机制。
- 当大于扩容后的时如果小于1024时新的容量是扩容前容量的2倍。
- 当大于扩容后的时如果大于1024时新的容量是扩容前容量的1.25倍即以0.25增加。
- 然后根据新的容量以及数据结构的类型大小进行内存对齐算出真实的新容量。
等等存储类型不都说了是使用SDS了为什么扩容还有什么存储类型的改变
原来是redis嫌结构体中的len
和free
字段也占两个int大小心疼所以要对SDS进行去肥增瘦。具体怎么做的大概如下
- 将字符串根据长度进行分类。
- 短字符串
len
和free
长度1字节就够了长字符串用2字节更长的用8字节。
- 短字符串
- 如何区分这些类型引入一个字段
flags
来表示对应的字符串是短的还是长的还是超长的。 - 但是这样又多一个字段了咋办没关系这个字段只有1字节一来一回就省下了。
所以在redis 3.2后SDS有以下5种存储类型当这五种类型在扩容时因为长度改变导致存储类型改变了就直接开辟新的内存即可。
最后看一下SDS对外提供的常用APISDS暴露对外的是指向buf
数组的指针。
String
字符串对象的内存模型大概为图中是raw编码的内存模型其他类型差别不大
字符串对象还是redis数据对象中唯一一种会被其他类型嵌套的对象。
字符串是大家最常用的redis对象基本的命令为
set
、get
、setnx
等设置单个kv和超时时间- mset、mget、msetnx等设置多个kv和超时时间
- append、setrange等用于对单个kv做改动
- incr、decr、incrby、decrby、incrbyfloat等用于原子计数
- setbit、getbit、bitcount、bitpos等字符串位操作
字符串对象的编码可以是int、raw或embstr
- 当一个字符串保存的是整数值且可以用long类型表示时其底层编码为int。
- 当一个字符串的长度小于等于39字节时其底层编码为embstr使用SDS来保存该值。
- 当一个字符串的长度大于39字节其底层编码为raw也是使用SDS来保存该字符串值。
上述的分界线32字节在不同版本不同3.2之前是393.2版本是44。
此处就有一个疑点
编码为embstr时使用SDS来实现编码为raw时也是使用SDS来实现。老周树人了。所以embstr与raw的区别在哪好处在哪
embstr与raw都是使用redisObject然后redisObject中的ptr指针指向SDS区别在于raw编码的字符串对象会调用两次内存来分别创建redisObject和SDS结构而embstr中的内存模型如下redisObject和SDS结构是连续存储的一次内存分配即可。这样可以更好地利用局部性原理。
此处又有一个疑点了3.2版本之后embstr与raw的分界线是44。这个数字很可疑呀我们刚刚才说过SDS有5个版本最小的sdshdr5
的最大容量稍微计算一下也才31个字节的长度当超过这个长度就要使用sdshdr8了使用sdshdr8
的时候len
字段有1B说明sdshdr8
可容纳128个字节的长度那为啥搞这个不零不整的44作为分界线
如果在小于31字节用sdshdr5
的话大于31字节后就要改变类型重新分配而因为embstr的redisObj与SDS结构体是连续的重新分配的开销更大。
所以embstr的底层结构是sdshdr8
为什么使用44作为分界线因为redis将redisObj与SDS结构体使用malloc方法一次分配当使用sdshdr8
时embstr最小占用空间为3字节加上redisObj的16字节一共19字节再选一个合适的大小比如64字节也就是redis一次性申请64字节的空间给embstr64-19-1=44这样44就出现了。为什么再-1因为尾部有一个\0
。这很redis很节省。
等等为什么是64字节我的理解是可能是cache大小。
再等等那sdshdr5
呢这样一看sdshdr5
没有用武之地了
对的在讲SDS的时候源码没贴全