redis面试(一)String底层剖析

前言

今天开始更新一些redis相关的知识点,初步计划是redis的数据类型,以及redis分布式锁
本章节主要是redis的String类型

字符串SDS

一般情况下我们认为的redis 字符串就是String,但是我这边要说的是底层String类型。

redis底层是C语言,但是C语言中的String有一定的缺陷,所以redis开发了一个自己的字符串类型 SDS 也就是simple dynaic string,专门的数据结构,保证字符串的完整性 动态字符串

c语言中的字符串是 r,e,d,i,s,\0 这种n+1的形式存储的,读取的时候一个个的读取,一直到 \0 这个数据
获取字符串长度的时候,还要遍历一遍

redis sds的字符串类型如下

struct sdshdr {
	int len;	// 已使用字节数= 字符串长度
	int free; 	// 未使用字节数
	char buf[];	// 字符串本身内容
}

比如:
sdshdr
free=0
len=5
buf-> [r,e,d,i,s,\0]

c字符串里规定除了末尾可以有一个\0空字符以外,内容里不能包含空字符,否则读到空字符就认定这个字符串结束了,所以导致c字符串只能保存文本,不能保存图片和音视频一类的二进制数据

二进制问题

re\0di\0s
re di s
如果你要是在内容里包含了很多空字符的话,你就不是二进制安全的了
但是sds实现了二进制安全,而且允许内容里包含\0,这是为什么呢,因为sds里有一个len,他是根据len来读取指定字节数量的字符的,而不是根据\0来判断字符串是否结束了,这是一个很关键的优化,通过这个就可以实现二进制安全了
但是redis还是遵守了末位是\0的c规范,这样保存的字符串,就可以复用c语言的一些函数了,因为c语言函数是这么规定的

c语言原生字符串,strlen,获取字符串长度,遍历,O(n),二进制不安全的,对于二进制数据来说,图片、视频、音频、压缩文件,这种数据都是以一大堆的二进制串来存储的,里面肯定会有很多\0空字符,对于c语言来说,读取的时候,一定要按照\0来截断,肯定不行的

二进制不安全,原生的c语言字符串没法安全的存储二进制数据

redis里面的sds,他里面有两个关键的字段,len和free,buf = [] 跟c语言没太大差别,len和free很厉害,free的作用,我们还没讲,len有2个作用,直接读取len,strlen,O(1),读取buf里的数据,不是遍历看到\0就停止

根据len读取指定字节数量的buf数字的内容,形成一个完整的内容,对于我们来说,redis里的sds存储二进制的数据,图片、音视频,就算buf里包含了很多\0空字符,根据len来读取,不是根据\0来截断,redis sds可以实现二进制安全性,安全的保存二进制数据

避免缓冲区溢出

redis和spark两个字符串,在内存地址里,连续的存储在了一起
[r,e,d,i,s,\0,s,p,a,r,k,\0]

str = redis

strcat(str, sentinel) -> 插入忘记了给sentinel字符串分配内存空间

c语言原生字符串可能搞出缓冲区溢出的问题,那就是说,r,e,d,i,s,s,p,a,r,k,连续两个字符串,redis和spark,结果我们要是用c语言的strcat函数,搞了一个strcat(str, ‘sentinel’),想要把sentinel拼接到redis字符串后面去,又忘了给sentinel提前分配内存空间,就会导致,r,e,d,i,s,s,e,n,t,i,n,e,l,直接覆盖掉spark了,这就是缓冲区溢出
[r,e,d,i,s,\0,s,e,n,t,i,n,e,l,\0]

sds(free=0,len=5,buf[r,e,d,i,s,\0]) -> sds(free=13,len=13,buf[r,e,d,i,s,s,e,n,t,i,n,e,l,\0])

sds会自动检查字符串扩容空间是否足够,不够就扩容空间,扩容完毕了再修改字符串的内容,比如sdscat就是这样的拼接字符串的函数,比如一开始sds的free=0,len=5,buf=redis,但是现在你要扩容加上sentinel了,此时就会先扩容,free=13,len=13,buf=redissentinel

内存预分配

除了扩容,他还会额外的给13个自己的free空间

c字符串在拼接或者截断的时候,要不然就得扩容数组,要不然就是释放空间内存,就是有频繁的内存重分配动作,这个内存重分配是很耗费时间的,涉及到os系统调用,redis频繁修改字符串,频繁内存重分配,那就真的是很尴尬

sds为了避免这种频繁内存重分配的问题,才设计了free,这个free可以实现内存预分配和惰性释放,每次sds的内存空间要进行扩容的时候,扩容完毕后,都会根据一个公式,提前计算出free,预分配一定的内存空间出来

扩容后,sds长度小于1mb,就把free设置为跟len一样,做一个双倍预分配,如果扩容后len大于1mb了,就把free设置为1mb就可以了,毕竟不能无限制的扩容下去,这个预分配后,就是底层c里的数组,留了一定字节数量的空间,但是没有真正使用

这样你后续再修改字符串,直接就用free空间就可以了,避免了频繁重分配内存了

惰性释放

如果缩短一个字符串,那么就会把字符串正常缩短,然后len设置为字符串长度,free就是原长度-现长度,以后如果要增长,还可以用free,如果确实要释放内存空间,sds提供了api来释放,但是他是不会立即释放掉的。而是在过一段时间之后,发现空间不足了,才会释放掉这部分空间

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木小同

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值