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