Redis系列之——这样的String你肯定不知道!
在上一篇文章中,我和大家介绍了Redis的前世今生,Redis的诞生就是为了解决mysql中IO性能的瓶颈,这一篇就和大家一起揭秘Redis神秘的面纱,第一个我们就来聊一聊Redis数据类型中的String!
Redis最常用的数据类型有五种
五种其实是Redis键值对中值存储的数据类型,而他们的底层数据结构一共有6种:分别是
数据类型和数据结构的对应关系如下图:
这张图会在未来几篇文章中反复出现,帮大家彻底了解Redis的基础类型。
今天我们就来聊一聊其中的string
众所周知,Redis是用C语言写的,那Redis为什么没有使用C原生的字符串,而是自己创建了一个简单动态字符串?(SDS simple dynamic string)
那么,Redis是怎么处理这些问题的呢?
Redis中的字符串数据是通过简单动态字符串(以下简称SDS)来存储数据的。
我们先来看看SDS的结构长什么样
你丫才掉发小课堂--详解SDS类型
SDS 结构中的字段 flags,表示的是SDS的类型。在Redis中SDS一共设计了5种类型,分别是
sdshdr5(未使用)
sdshdr8
sdshdr16
sdshdr32
sdshdr64
这5种类型的主要区别就在于,它们数据结构中的len和alloc,这两个字段占据的大小不同,也就是这个结构体能存储的长度不同。下面他们是结构体的源码:
从源码我们可以看出来,其实最大的不同就是len和alloc所代表的字节数不同,那么比如sdshdr8中这2个字段的类型uint8_t,也就是这个类型结构最大存储的字符长度为256(2的8次方),其他的以此类推。
这样的设计目的:Redis是基于内存的,而内存永远都是珍贵的资源,每一个字节都很重要,所以针对不同大小的字符串使用不同的结构,也是为了节省内存资源。
在SDS中,除了上面解释过的flags,对比于C传统的char[],SDS新增了2个参数,实际占用长度len 和总分配长度alloc。
1. 获取字符串长度的复杂度问题
上面我们说过,c语言遍历一下字符串的复杂度是o(n)。
对于SDS来说,我们有了长度的参数,那么我们的复杂度就是o(1),这样在进行拼接等操作的时候不需要再次遍历了。
2. 存储二进制数据
C字符串不能存储二进制数据的原因是只能根据'\0'来判断数据是否结束,不能保证其完整性,但因为SDS的len属性,无论你数据里有多少'\0'都没关系,我是根据 len 属性值来判断数据长度的,必定是完整的,所以SDS可以安全地存储二进制数据,这样图片音频等二进制数据也可以存储。
你丫才掉发小课堂
Q:SDS你都有len了,那为啥还要跟C字符串一样在字符串后面加'\0',是不是多此一举了?
A:SDS如果保持和C字符串一样的特性,就不用专门编写多余的函数了,在一定的情况下可以直接用C语言的函数,避免了不必要的重写。
3. 分配内存空间问题
上面还在存在一个问题是在使用拼接等操作的时候C语言是需要重分配空间的,而这个又是非常的消耗资源。SDS提供的2个特性就是空间预分配 和 惰性空间释放
空间预分配
空间预分配源码
在真正执行拼接等操作之前,会先计算下空间是否满足
所以当有N次拼接操作时候,一定需要N次的内存重分配操作,现在最多只需要N次。
比如你第一次拼接后,剩余的长度(alloc - len)就大于后续要拼接的字符串长度之和了,那其实就只有1次内存重分配操作,就可以进行至少2次拼接操作,所以说最多N次,这也就可以省下很多分配空间的消耗。
惰性空间释放
当有缩短SDS字符串操作时,程序并不立即把空闲出来的字节释放掉,而是留给到惰性删除策略等机制释放。
具体的表现就是:
在做删除操作的时候,当删掉N个字符后,alloc-len(也就是剩余空间)就增加N,换个说法,就是你删除的字符并没有立即被系统回收,这样当你短时间内再次拼接的时候,就不会申请空间分配了。
只有到了对应策略或者手动去删除的时候才会执行以下的代码:
代码的逻辑就是删除多余的空间直到没有浪费为止。
前面我们聊完了SDS,已经大概的知道了SDS是什么样的,解决了什么问题,这里给大家提出一些更深层的问题:
在我们真实使用Redis中string的时候,除了SDS的存储空间,还存在一个Redis本身的结构体,也就是RedisObject。
以上是RedisObject源码,
为了形象的展示占据的大小:
这里暂时不对RedisObject展开讲,我们来看看当RedisObject和SDS在一起的时候大概是个什么样子?
上图表示的就是普通情况下RedisObject和SDS的关系。
作为Redis最常用的结构之一,针对String也做了很多的优化
1. 数字类型的字符串
对于数字类型的字符串,Redis在RedisObjec中的指针就直接赋值为整数数据了,这么指针的开销就省去了。
2. embstr编码方式(embed:嵌入式)
在保存的是字符串数据,如果字符串小于等于44字节时(为什么是44呢?读到这里可以思考一下),Redis就会将RedisObjec和SDS构建成一块连续的内存区域,既可以提升查询的速度(少了一次指针的跳转),也可以防止内存碎片的产生。
我们先来看下这里判断的源码:
这里我们来看下createEmbeddedStringObject的部分源码
这里做的事情核心
这个时候内存块包含如下几部分:
3. raw编码模式
在保存的是字符串数据,字符串大于44字节时,Redis就不像embstr编码方式一样把RedisObject和SDS存放在一起,这时RedisObject中的*ptr就会发挥真正的作用,Redis在内存中重新分配一块空间给SDS,并用*ptr指向这个SDS。
这里我们来看下createRawStringObject的部分源码
这里就比较简单了,一共做了这2件事:
最后再给大家把所有的编码格式放一起汇总一下:
Redis关于String的介绍已经结束了,但有个问题我还没给大家解释,这个问题大家可以思考一下,欢迎在评论区讨论你的答案或者联系我聊一聊,下期会为大家解答!
问题:embstr编码方式和raw编码方式的转折点为什么是44字节?
更多知识分享,欢迎关注点赞收藏评论~
留言与评论(共有 0 条评论) “” |