面试场景面试官:Redis有哪些数据类型?我:String、List、set、zset、hash面试官:没了?我:哦哦,还有HyperLogLog、bitMap、GeoHash、BloomFilter面试官:就这些?回家等通知。前言我肯定100%的人能回答第一个答案,但能回答第二个答案的人可能不多,但这不是我今天要讨论的话题。我会根据自己面试的答题思路,以及他作为面试官想听到的标准答案,给大家出一期Redis基础类型的文章(系列文章)。写到这里还是有很多体会的,不知道当初有多少人和我一样。面试官问有哪些类型,回答完五种就结束了。如果你是这样的,可以在评论区留言,让我看看有多少人是这样的。但是,面试至少半小时开始,没有上限。这么重要的五个知识点,你一句话就回答完了。这个结果是你想要的吗?是面试官想要的吗?再问你一个问题,你可能会一头雾水:String在Redis底层是怎么存储的?这些数据类型如何存储在Redis中?Redis快的唯一原因是单线程和基于内存的吗?宝贝,你有没有摸到知识的盲点?别慌,我以前也是这样。本来以为把那五种都背完就完了,结果面试官安排了一波。之后,我刻苦练习,终于好起来了。现在我对缓存非常熟悉了。你不会有事的,有我在,好好的。字面意思Redis是用C语言开发的,C语言本身就有字符类型,但是Redis并没有直接采用C语言的字符串类型,而是自己构建了动态字符串(SDS)的抽象类型。就像这个命令,其实我在Redis中创建了两个SDS,一个是名为aobing的KeySDS,一个是名为cool的ValueSDS,即使是字符类型的List,也是由很多SDS组成的Key和Value仅此而已。SDS除了在Redis中作为字符串使用之外,还作为缓冲区(buffer)使用,所以这里大家还是有点疑惑,C语言中的字符串不好用为什么要用SDS呢?SDS是什么样子的?有什么好处?为此,我找到了Redis的源码,可以看到SDS值的结果大概是这样的。源码在GitHub上开源,搜索即可找到。structsdshdr{intlen;intfree;charbuf[];}回到最初的问题,为什么Redis使用自己新开发的SDS而不是C语言的字符串呢?好吧,让我们看看它们之间的区别。SDS和C串的区别在于计数方法不同。C语言中对字符串长度的统计完全是遍历,从头到尾,直到找到一个空字符才停止,从而统计出字符串的长度,并在此得到长度的时间way复杂度上为0(n),大致如下:但是这种计数方式会留下隐患,所以Redis没有使用C字符串,后面会提到。至于Redis,上面的结构我已经给大家展示过了。它本身保存了长度信息,所以我们获取长度的时间复杂度为0(1)。你有没有发现Redis快的一个小细节?还没完,还不止这些。杜绝缓冲区溢出字符串拼接是我们经常做的操作。在C和Redis中也是很常见的操作,但是问题是C并没有记录字符串的长度。一旦我们调用拼接函数,如果不提前计算内存,就会发生缓冲区溢出。比如原来的字符串是这样的:后面需要拼接,但是你没有计算内存,结果可能是这样的:这是你想要的结果吗?显然,不,你的结果被意外修改了。如果这是一个在线系统,这不就结束了吗?那么Redis是如何避免这种情况的呢?我们都知道它的结构体中存放的是当前长度和free未使用的长度。很简单。既然已经做了拼接操作,我再判断有没有能放下的。如果长度足够,直接执行。如果还不够,那我再扩充。在Redis源码中可以看到对应的API。后面就不一一贴出源码了。有兴趣的可以自己查一下。你需要一点C语言基础。减少由修改字符串引起的内存重新分配的次数。C语言字符串底层也是数组。每创建一次,就创建一个长度为N+1的字符。多出的1是为了保存空字符。字符也是一个坑,但不是本链接讨论的内容。Redis是一个缓存数据库。如果我们需要频繁拼接和截断字符串,如果我们写代码忘记重新分配内存,可能会导致缓冲区溢出和内存泄漏。内存分配算法是比较耗时的,且不说你会不会忘记重新分配内存,就算你全部记住了,对于缓存数据库来说,这样的开销是应该避免的。为了避免C字符串等缺陷,Redis采用了两种方案来最大化性能和最大化空间利用:空间预分配:当我们扩展SDS时,Redis会为SDS进行分配。内存,并根据特定的公式,分配额外的空闲空间,额外的1byte空间(这1byte也是用来存放空字符的),这样我们就可以避免连续执行字符串加法带来的内存分配消耗。比如现在有这样一个字符:我们调用拼接函数,字符串的边比较长,Redis会根据算法为他计算一个空闲值作为备份:如果我们继续拼接,你会发现使用备用免费。这次省略了内存重分配:Lazyspacerelease:刚才说到多余的空间会被预分配,很多朋友会担心内存泄漏或者浪费。别着急,Redis老大帮我们想好了。当我们进行一次字符串缩减操作后,redis不会马上回收我们的空间,因为它可以阻止你继续添加操作,这样可以减少分配空间的消耗,但是当你再次操作的时候,多余的空间还是没有用的,有时候,Redis还是会回收相应的空间,防止内存的浪费。还是一样的字符串:当我们调用delete函数时,空闲空间不会立即释放:如果我们需要继续添加这个空间,可以使用,减少内存重新分配,如果不需要空间,调用函数即可并删除它:如果你仔细看二进制安全,你一定看到我不止一次提到空字符,即'0'。C语言通过判断空字符来确定一个字符的长度,但是数据结构有很多。空字符往往穿插在中间,比如图片、音频、视频、压缩文件的二进制数据,比如下面这个词,只能识别前面的字符,不能识别后面的字符。对于我们开发者来说,这样的结果显然不是我们想要的吧?Redis没有这个问题。他不保存字符串的长度吗?他不判断空字符,只判断长度对不对。所以redis经常被我们用来保存各种二进制数据。反正使用率很高,经常用来保存小文件的二进制文件。资料参考:Redis设计与实现总结你有没有发现一个小小的SDS有这么多理由?之前就知道Redis很快。Redis顶多是单线程,多路IO多路复用,基于内存的操作。现在,我可以扩展它吗?本文为系列文章的第一章,后续会陆续更新。不知道你喜不喜欢这种类型的文章。大家一起去面试,同一个问题,有的人能通过,有的人不能。每个人都经常归咎于自己的教育背景和过往经历,但你可以问问自己,你是否深究过背后的细节?细节往往是最重要的,而人们知道的最少。我认为正是这些细节让你和其他人有所不同,并获得了报价。谁不知道呢?
