Redis字符串底层实现

Redis 中最常用的可能就是字符串了吧,我们通过 set 命令可以存储一个键值到缓存中,key 是一个字符串,value 也是一个字符串,可是你知道 Redis 中的这个字符串的底层结构吗?

探秘SDS

大家的可能都知道 Redis 是基于 C 语言实现的,但是其实 Redis 并没有直接使用 C 语言里面的字符串,即传统的以空字符(\0)结尾的字符数组,而是字节构建了一种名为 SDS (Simple Dynamic String)的简单动态字符串。

Redis 中几乎所有用到字符串的地方都是使用的 SDS 这种结构,而像 C 语言中的字符串只会用在不会对字符串进行修改的地方,比如打印日志。

下面我们来看一下 SDS 的结构

struct sdshdr{
	int len;
	int free;
	char buf[];
}

buf即字节数组,用于保存我们的字符串,跟 C 语言中的字符串一样,末尾以\0结尾,它这么做的目的是为了可以重用一部分 C 语言的函数,当然\0的管理不需要我们操心,Redis 提供的API已经为我们做了这个事。

其中len记录了buf数组中已经使用的字节数量,也就是字符串的长度(这个长度不包括末尾的\0),free记录了buf数组中没有使用的字节数量。
在这里插入图片描述
下面我们来看看 Redis 为什么要这么设计。
len的设计让我们可以以常数的复杂度来获取字符串的长度,当我们想要获取字符串长度时,只需要访问一下这个len属性即可,而不必遍历字符数组。

free的设计可以减少修改字符串时重新分配内存的次数,比如 Redis 中支持 append 操作。如果数组没有冗余空间,那么追加操作必然涉及到分配新数组,然后将旧内容复制过来,再 append 新内容。如果字符串的长度非常长,这样的内存分配和复制开销就会非常大。

扩容

SDS在长度小于 1M 之前,扩容时采用的加倍策略,也就是保留 100% 的冗余空间,这句话的意思就是SDS在进行空间扩展的时候,不但会为SDS分配修改后所需要的空间还会为它分配额外的未使用空间,即当修改后的len如果小于1M,那么此时free的值和len相同,这个也就是100% 的冗余空间的意思。

当修改后 SDS len 超过 1M 之后,为了冗余空间过大而导致浪费,扩容后只会给 free 分配 1M 大小的冗余空间。

这里需要注意的是 Redis 中默认字符串最大长度为512M。

缩容

对于缩容 SDS 采用的是惰性空间释放的策略,即 SDS 缩短后不会立即重新分配内存来回收多余的字节,而是通过 free 将这些字节数量记录起来,等待将来的使用,不过也可能这些空间永远用不上了 ,造成了内存泄漏,不用担心,SDS 也提供了相应的API可以让我们手动释放未使用空间。

Redis 对象头

Redis 中所有的对象都有一个结构头:

struct RedisObject {
 int4 type; 
 int4 encoding; 
 int24 lru;
 int32 refcount; 
 void *ptr; 
} robj;

不同的对象的类型 type(4bit)是不同的,即使是同一个类型的 type 可能也会有不同的存储形式 encoding(4bit)

lru 使用了 24 个 bit 来记录 LRU 信息。

refcount 即每个对象的引用计数,当引用计数为零时,对象就会被销毁,内存被回收。

ptr 指针将指向对象内容 (body) 的具体存储位置。

这样一个 RedisObject 对象头需要占据 16 字节的存储空间。

SDS 两种存储方式

SDS 有两种存储方式,在长度较短时使用 emb 形式存储 (embeded),当长度超过 44 时,使用 raw 形式存储。

embstr 将 RedisObject 对象头和 SDS 对象连续存在一起,使用 malloc 方法一次分配。

而 raw 存储形式不一样,它需要两次 malloc,两个对象头在内存地址上一般是不连续的。
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值