你知道的越多,不知道的就越多,业余的像一棵小草!
你来,我们一起精进!你不来,我和你的竞争对手一起精进!
编辑:业余草
来源:cnblogs.com/wyc1994666/p/10669212.html
推荐:https://www.xttblog.com/?p=5112
最近一位粉丝在找工作,面试时在 Redis 相关问题上可栽了跟头。在面试前按常规套路准备了一下,比如 Redis 的常用 5 种数据结构,Redis 持久化策略,Redis 实现分布式锁,简单发布订阅等等都准备了,当时不知天高地厚以为十拿九稳了,可是万万没想到我终究还是在Redis 的被问的第一个问题上翻船了~~
面试官 :看你简历上写了熟悉常用数据结构,都有哪些说说 本人 :常用有 5 种,string,list,set,zset,hash(内心很得意)
面试官 :那你说说都用过哪些数据结构 本人 :用的最多的是 string,通常会把 json 字符串存进去
面试官 :那你知道 Redis 内部是怎么实现它的 string 的么?本人 :呃~,我了解 Redis 是用C语言写的,至于具体实现就不清楚了~
到此一面卒~~~
有相同经历的朋友么?
回去后这位粉丝恶补了一下 Redis 有关原理性的知识点,今天我们在语言聊天时,又谈到来这个话题,于是就花时间总结了一下这次的面试经历,也就是本文,分享给大家。
本篇会讲一下内容:
- Redis 字符串的实现
- Redis 字符串的性能优势
- Redis 字符串的实现
Redis 虽然是用 C 语言写的,但却没有直接用 C 语言的字符串,而是自己实现了一套字符串。目的就是为了提升速度,提升性能,可以看出 Redis 为了高性能也是煞费苦心。
❝Redis构建了一个叫做简单动态字符串(Simple Dynamic String),简称SDS。
❞
SDS 代码结构
struct sdshdr{
// 记录已使用长度
int len;
// 记录空闲未使用的长度
int free;
// 字符数组
char[] buf;
};
SDS ?什么鬼?可能对此陌生的朋友对这个名称有疑惑。只是个名次 2 而已不必在意,我们要重点欣赏借鉴 Redis 的设计思路。
下面画个图来说明,一目了然。
Redis 的字符串也会遵守 C 语言的字符串的实现规则,即最后一个字符为空字符。然而这个空字符不会被计算在 len 里头。
SDS 动态扩展特点
SDS 的最厉害最奇妙之处在于它的 Dynamic。动态变化长度。举个例子
如上图所示刚开始s1 只有5个空闲位子,后面需要追加' world' 6个字符,很明显是不够的。那咋办?Redis会做一下三个操作:
- 计算出大小是否足够
- 开辟空间至满足所需大小
- 开辟与已使用大小 len 相同长度的空闲 free 空间(如果len < 1M)开辟 1M 长度的空闲 free 空间(如果len >= 1M)
看到这儿为止有没有朋友觉得这个实现跟 Java 的列表 List 实现有点类似呢?看完后面的会觉得更像了。
Redis 字符串的性能优势
- 快速获取字符串长度
- 避免缓冲区溢出
- 降低空间分配次数提升内存使用效率
快速获取字符串长度
在看下上面的 SDS 结构体:
struct sdshdr{
// 记录已使用长度
int len;
// 记录空闲未使用的长度
int free;
// 字符数组
char[] buf;
};
由于在 SDS 里存了已使用字符长度 len,所以当想获取字符串长度时直接返回 len 即可,时间复杂度为 O(1)。如果使用 C 语言的字符串的话它的字符串长度获取函数时间复杂度为 O(n),n 为字符个数,因为他是从头到尾(到空字符'\0')遍历相加。
避免缓冲区溢出
对一个 C 语言字符串进行 strcat 追加字符串的时候需要提前开辟需要的空间,如果不开辟空间的话可能会造成缓冲区溢出,而影响程序其他代码。如下图,有一个字符串 s1="hello" 和 字符串 s2="baby",现在要执行 strcat(s1,"world"),并且执行前未给 s1 开辟空间,所以造成了缓冲区溢出。
而对于 Redis 而言由于每次追加字符串时都会检查空间是否够用,所以不会存在缓冲区溢出问题。每次追加操作前都会做如下操作:
- 计算出大小是否足够
- 开辟空间至满足所需大小
降低空间分配次数提升内存使用效率
字符串的追加操作会涉及到内存分配问题,然而内存分配问题会牵扯内存划分算法以及系统调用所以如果频繁发生的话影响性能,所以对于性能至上的 Redis 来说这是万万不能忍受的。所以采取了一下两种优化措施
- 空间与分配
- 惰性空间回收
空间预分配
对于追加操作来说,Redis 不仅会开辟空间至够用而且还会预分配未使用的空间(free)来用于下一次操作。至于未使用的空间(free)的大小则由修改后的字符串长度决定。
当修改后的字符串长度 len < 1M,则会分配与 len 相同长度的未使用的空间(free) 当修改后的字符串长度 len >= 1M,则会分配 1M 长度的未使用的空间(free)
有了这个预分配策略之后会减少内存分配次数,因为分配之前会检查已有的 free 空间是否够,如果够则不开辟了~
惰性空间回收
与上面情况相反,惰性空间回收适用于字符串缩减操作。比如有个字符串 s1="hello world",对 s1 进行 sdstrim(s1," world") 操作,执行完该操作之后 Redis 不会立即回收减少的部分,而是会分配给下一个需要内存的程序。当然,Redis 也提供了回收内存的 api,可以自己手动调用来回收缩减部分的内存。
好了,这个面试题就总结到这里,到此为止结束了~下次在遇到这个问题可以侃侃而谈了!