Redis高频面试题:10w+QPS的Redis真的只是因为单线程和基于内存?

你以为Redis这么快仅仅因为单线程和基于内存?

那么你想得太少了,我个人认为Redis的快是基于多方面的:不但是单线程和内存,还有底层的数据结构设计,网络通信的设计,主从、哨兵和集群等等方面的设计~

下面,我将360°为你揭开RedisQPS达到10万/秒的神秘面纱。

首先值得称赞的第一点:Redis底层使用的数据结构很多,但是却没有直接使用这些数据结构来实现键值对数据库,而是基于数据结构创建了一个对象(redisObject)系统。(是不是觉得有点面向对象编程的意思~)

对象系统里面包括了字符串对象,列表对象,哈希对象、集合对象和有序集合对象。

使用对象的好处:

一个对象怎么设置不同的数据结构实现?

在讲解前,我们必须要了解Redis对象的结构。

它三个重要的部分:type属性、encoding属性,和ptr属性。

我们用字符串对象为例:

我们都知道,Redis的SET命令其实是针对字符串的,但是它也可以设置数值。那底层是怎么做的呢?

它会将String对象的encoding属性标识为REDIS_ENCODING_INT,表示这个键对应的值是Long类型的整数。

而当我们利用APPEND命令往值后面添加字符串呢?

此时会将String对象的encoding属性的标识为REDIS_ENCODING_RAW,表示这个值此时是简单动态字符串。

正是因为使用对象,通过type、encoding和prt属性,使得同一个对象可以适应在不同的场景下,使得不同的改变不需要创建新的键值对,这样使得Redis的对象使用效率非常的高。

Redis的字符串对象采用三种编码:int、embstr和raw。

int编码就不用说了,就是为了兼容SET命令可以设置数值。

而embstr和raw最大的区别就是内存分配操作次数:

Redis中字符串对象的底层是使用SDS(SimpleDynamicString)实现的。

SDS有三部分:

首先介绍一下使用len属性和free属性的好处:

得益于SDS有len属性,获取字符串长度的复杂度为O(1);

得益于SDS有free属性,可以杜绝缓冲区溢出,字符串扩展前可以根据free属性来判断是否满足直接扩展,不满足则需要先执行内存重分配操作,然后再扩展字符串。

我们都知道修改字符串长度很有可能导致触发内存重分配操作,但是Redis对于内存重分配有两个优化策略:

空间预分配:

惰性空间释放:

目前来说,Redis会在初始化服务器时,创建一万个字符串对象,这些对象包含了从0到9999的所有整数值,当服务器需要用到值为0到9999的字符串对象时,服务器就会使用这些共享对象,而不是新创建对象。

当然了,我们还可以通过修改redis.h/REDIS_SHARED_INTEGERS常量来修改创建共享字符串对象的数量。

我们都知道Redis是使用C语言开发的,所以SDS一样遵循C字符串以空字符结尾的惯例,所以SDS可以重用很多库定义的函数。

简单介绍一下ziplist的结构:

压缩列表是一种为节约内存而开发的顺序型数据结构,所以在Redis里面压缩列表被用做列表键和哈希键的底层实现之一。

正是利用压缩列表,不但使得数据非常紧凑而节约内存,而且还可以利用它的结构来做到非常简单的顺序遍历、逆序遍历,O(1)复杂度的获取长度和所占内存大小等等。

整数集合(intset)是Redis用于保存整数值的集合抽象数据结构,它可以保存类型为int16_t、int32_t或者int64_t的整数值,并且保证集合中不会出现重复元素。

我们先看看整数集合的结构:

intset一开始不会直接使用最大类型来定义数组,而是利用升级操作,当元素的值达到一定长度时,会重新为数组分配内存空间,并将数组里的旧元素的类型进行升级。

这样做好处:

因为整数集合没有降级操作,所以从另外一个角度看,升级操作其实也会浪费内存:如果整数集合里只有一个数值是int64_t,而其他数值都是小于它的,但是整数集合的编码将还是保持INTSET_ENC_INT64,就是说,小于int64_t的整数还是会用int64_t的空间来保存。

每当别人问Redis为啥这么快?脱口而出的不是基于内存就是基于单线程。

Redis使用基于Reactor模式实现的网络通信,它使用I/O多路复用(multiplexing)程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器。

当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时,与操作相对应的文件事件就会产生,这时文件事件分派器就会调用套接字之前关联好的事件处理器来处理这些事件。

因为Redis是单线程的,所以I/O多路复用程序会利用队列来控制产生事件的套接字的并发;队列中的套接字以有序、同步、每次一个的方式分派给文件事件分派器。

多种I/O复用机制:

常见的I/O复用机制有很多种,例如select、epoll、evport和kqueue等等。

Redis对上面的多种I/O复用机制都进行了各自的封装,在程序编译时会自动选择系统中性能最高的I/O多路复用函数库来作为Redis的I/O多路复用程序的底层实现。

我们都知道,文件事件的发生都是随机的,因为Redis服务器永远不可能知道客户端下次发送命令是什么时候,所以程序也不可能一直阻塞着直到发生文件事件。

Redis2.8前的复制功能:

缺点:

假设主从服务器断开连接,当从服务器重新连接上后,又要重新执行一遍同步(sync)操作;但是其实,从服务器重新连接时,数据库状态和主服务器大致是一样的,缺少的只是断开连接过程中,主服务器接收到的写命令;每次断线后都需要重新执行一遍完整的同步操作,这样会很浪费主服务器的性能,毕竟BGSAVE命令要读取此时主服务器完整的数据库状态。

Redis2.8后对复制算法进行了很大的优化:

在命令传播阶段,从服务器默认会以每秒一次的频率,向主服务器发送命令:REPLCONFACK,其中replication_offet是从服务器当前的复制偏移量。

心跳检测的三大作用:

min-slaves-to-write3min-slaves-max-lag10解释:那么在从服务器的数量少于3个,或者三个从服务器的延迟(lag)值都大于或等于10秒时,主服务器将拒绝执行写命令,这里的延迟值就是上面提到的INFOreplication命令的lag值。3、哨兵模式的订阅连接设计Sentinel不但会与主从服务器建立命令连接,还会建立订阅连接。

在默认情况下,Sentinel会以每两秒一次的频率,通过命令连接向所有被监视的主服务器和从服务器发送PUBLISH命令,命令附带的是Sentinel本身的信息和所监听的主服务器的信息;接着接收到此命令的主从服务器会向_sentinel_:hello**频道发送这些信息。

而其他所有都是监听此主从服务器的Sentinel可以通过订阅连接获取到上面的信息。

这也就是说,对于每个与Sentinel的服务器,Sentinel既通过命令连接向服务器的sentinel:hello频道发送信息(PUBLISH),又通过订阅连接从服务器的sentinel:hello频道接收信息(SUBSCRIBE)。

通过这种方式,监听同一个主服务器的Sentinel们可以互相知道彼此的存在,并且可以根据频道消息更新主服务器实例结构(sentinelRedisInstance)的sentinels字典,还可借此与其他Sentinel建立命令连接,方便之后关于主服务器下线检查、选举领头Sentinel等等的通信。

Redis集群中的各个节点通过Gossip协议来交换各自关于不同节点的状态信息,其中Gossip协议由MEET、PING、PONG三种消息实现,这三种消息的正文都由两个cluster.h/clusterMsgDataGossip结构组成。

利用Gossip协议,可以使得集群中节点更新的信息像病毒一样扩散,这样不但扩散速度快,而且不需要每个节点之间都发送一次消息才能同步集群中最新的信息。

至此,我自己能想到的使得Redis性能优越的设计都在这里了。当然了,它的厉害之处远远不止这些~

大家都知道,使用Redis是非常简单的,来来去去就几个命令,但是当你深入Redis底层的设计和实现,你会发现,这真的是一个非常值得大家深究的开源中间件!!!

THE END
1.解锁“二分魔法”:让算法题轻松找到答案的秘密(1)二分查找算法(Binary Search)是一种高效的查找算法,核心思想是“分而治之”,适用于在一个有序的数组中快速定位目标值。通过反复将查找范围对半缩小,二分算法显著降低了查找的复杂度,从线性 (O(n)) 降到对数级别 (O(\log n))。它的关键在于精确判断中间元素与目标值的关系,并灵活调整上下界,从而迅速锁定答案https://blog.csdn.net/effort123_/article/details/144163803
2.鬼才数学老师:独创14个速算法,学生计算又准又快,大脑堪比计算机小学时期的计算能力,可以说决定这孩子学生时代的数学成绩,因为在计算上能避免出错的孩子,说明他是一个细心、反应能力快速的孩子。反之则是粗心大意的孩子,而细心是学好数学最需要的一种能力。 当了几十年的数学老师,在教学几十年的过程中也接触了很多家长和孩子。经常会有家长和我抱怨为什么同一个班,同一个老师教https://www.163.com/dy/article/DF92NJUS0516O6KF.html
3.算法之路到底该怎么走希望大家能够愿意尝试接触算法,了解算法,学习算法,即使能够坚持学习算法的人少之又少,但坚持的过程本身就是一种磨练。希望大家都能够收获绞劲脑汁AC时的自豪,看懂大佬题解时对其绝妙算法的赞叹!https://www.jianshu.com/p/cabc966267d6
4.诸葛武侯巧连神数,到底该怎么算?一、算法: 第一课 混沌初开,乾坤及定,日月合璧,凤凰合鸣。 第二课 苍蝇之飞,不过数步,附于骥尾,则腾千路。 第三课 莫言多,莫行过,虽是千伶百俐,不如一推二摩。 第四课 绝妙绝妙,云无心,以出岫鸟倦飞,而归巢,花艳艳,鱼跃跃几般佳。 https://www.360doc.cn/article/70612587_944534817.html
5.算法之美(Algorithms)书评我在08年初得知这本书,那会我还很奇怪:都什么年月了,怎么还有人写算法教材——这么“经典”的工作,不是上个世纪就被人做完了吗。读了这本Algorithms,我才知道:这才是我心中的算法书,我等待这样一本书已经很多年了。它的确当得起这个名字。书的三位作者:Sanjoy Dasgupta, Papadimitriou, Umesh Vazirani。其中,https://book.douban.com/review/1325850/
6.[PlantSimulation]GAWizard遗传算法的应用PlantSimulation除了提供常规的建模功能外,还可以通过额外的库来进行模型的优化,比如说遗传算法、瓶颈分析等,今天主要讲一下Tools中GAWizard的使用方法。 遗传算法是20世纪60~70年代主要由美国 Michigon 大学 John Holland 教授提出. 其内涵哲理启迪于自然界生物从低级、简单到高级、复杂,乃至人类这样一个漫长而绝妙的进https://www.pianshen.com/article/57411863425/
7.研修日志数学(精选9篇)因此,引导学生对算法进行优化是很有必要的,不过教师必须注意以下三点:第一、优化的主体是学生,而不是教师,教师绝不能定向性地规定学生用统一的方法去解题,而应把优化的主动权交给学生,优化的过程应该是学生进行自我反思,进一步比较、探索的过程,究竟哪一种算法好,要依不同的学情而定,只要学生通过比较、分析,认识https://www.360wenmi.com/f/filewe6q9siy.html
8.组合优化理论第11章启发式算法.pdf第11章 启发式算法 主讲人:陈安龙 2017年11月5 日 ? Combination Optimization PPT was designed by Chen Anlong, @UESTC.edu.cn 1 §1 模拟退火算法 §2 模拟遗传算法 2017年11月5日 ? Combination Optimization PPT was designed by Chen Anlong, @UESTC.edu.cn 2 本章介绍的启发式算法也称智能算法https://max.book118.com/html/2022/0612/6214234213004155.shtm
9.比你更了解你:算法已经无可匹敌了吗?例如如何用“高斯绝妙定理”[1]正确拿一块比萨。这实在太好了。我再也不用为耷拉下来的比萨发愁了(馅儿会掉),学会卷着拿,精准地送到嘴里。这种感觉很棒,就像一个外国人学会了拿筷子夹花生。除了高斯,黑客们的超级明星还有乔治·布尔,他们的理论(高斯函数[2]、布尔逻辑[3])和更多数学分支,成就了今天的算法https://china.caixin.com/m/2017-08-22/101133731.html
10.最快的开平方sqrt算法,供赏析51CTO博客最快的开平方 sqrt 算法,供赏析,最快的开平方sqrt算法,供赏析https://blog.51cto.com/990487026/1941559