Redis为什么会这么快

简介:大家好,我是枫哥🌟一线互联网的IT民工、📝资深面试官、🌹Java跳蚤网课堂创始人。拥有多年一线研发经验,曾就职过科大讯飞、美团网、平安等公司。在上海有自己小伙伴组建的副业团队,目前业余时间专注Java技术分享,春招/秋招/社招/跳槽,一对一学习辅助,项目接活开发。

🎉🎉扫码左侧二维码,加入群聊,一起学习,一起进步!

🌟 欢迎关注 🌟 收藏 🌹留言 🌹

🍊🍊:文末送福利

目录

1.纯内存KV操作

2.单线程操作

3.I/O 多路复用

4.Reactor 设计模式


1.纯内存KV操作


Redis的操作都是基于内存的,CPU不是 Redis性能瓶颈,,Redis的瓶颈是机器内存和网络带宽。
在计算机的世界中,CPU的速度是远大于内存的速度的,同时内存的速度也是远大于硬盘的速度。redis的操作都是基于内存的,绝大部分请求是纯粹的内存操作,非常迅速。

2.单线程操作


使用单线程可以省去多线程时CPU上下文会切换的时间,也不用去考虑各种锁的问题,不存在加锁释放锁操作,没有死锁问题导致的性能消耗。对于内存系统来说,多次读写都是在一个CPU上,没有上下文切换效率就是最高的!既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章的采用单线程的方案了
Redis 单线程指的是网络请求模块使用了一个线程,即一个线程处理所有网络请求,其他模块该使用多线程,仍会使用了多个线程。

3.I/O 多路复用


为什么 Redis 中要使用 I/O 多路复用这种技术呢?
首先,Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回,这会导致某一文件的 I/O 阻塞导致整个进程无法对其它客户提供服务,而 I/O 多路复用就是为了解决这个问题而出现的。
IO一般分为磁盘IO和网络IO,这里我们主要关注网络IO。一次完整的网络IO过程如下所示:


从上图可以看出,数据无论从网卡到用户空间还是从用户空间到网卡都需要经过内核。

阻塞 I/O
当应用程序调用一个 IO 函数,其底层会委托操作系统的recvfrom()去完成,当数据还没有准备好时,revfrom会一直阻塞,等待数据准备好。当数据准备好后,从内核拷贝到用户空间,recvfrom 返回成功,IO函数调用完成。过程如下所示:

阻塞IO模型的优点是编程简单,但缺点是需要配合大量线程使用。应用进程没接收一个连接,就需要为此连接创建一个线程来处理该连接上的读写任务。

非阻塞 I/O
调用进程在等待数据的过程中不会被阻塞,而是会不断地轮询查看数据有没有准备好。当数据准备好后,将数据从内核空间拷贝到用户空间,完成IO函数的调用。等待数据的过程是非阻塞的,但数据拷贝时仍是阻塞的。过程如下所示

非阻塞io的优点在于可以实现使用一个线程同时处理多个连接的需求,减少线程的大量使用。缺点在于要不断地去轮询检查数据是否准备好,比较耗费CPU。

I/O复用模型
为了解决非阻塞IO不断轮询导致CPU占用升高的问题,出现了IO复用模型。IO复用中,使用其他线程帮助去检查多个线程数据的完成情况,提高效率。
Linux中提供了select、poll和epoll三种方式来实现IO复用。一个线程可以对多个IO端口进行监听,当有读写事件产生时会分发到具体的线程进行处理。过程如下所示:


IO复用只需要阻塞在select,poll或者epoll,可以同时处理和管理多个连接。缺点是当 select、poll或者epoll 管理的连接数过少时,这种模型将退化成阻塞 IO 模型。并且还多了一次系统调用:一次 select、poll或者epoll 一次 recvfrom。

(1)select==>时间复杂度O(n)


它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长,select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是: 单个进程可监视的fd数量被限制,即能监听端口的大小有限。


(2)poll==>时间复杂度O(n)


poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的.
epoll==>时间复杂度O(1)
epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1)),内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销。
select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的

4.Reactor 设计模式


Redis基于Reactor模式开发了自己的网络事件处理器,称之为文件事件处理器(File Event Hanlder)。文件事件处理器由Socket、IO多路复用程序、文件事件分派器(dispather),事件处理器(handler)四部分组成。文件事件处理器的模型如下所示:

IO多路复用程序会同时监听多个socket,当被监听的socket准备好执行accept、read、write、close等操作时,与这些操作相对应的文件事件就会产生。IO多路复用程序会把所有产生事件的socket压入一个队列中,然后有序地每次仅一个socket的方式传送给文件事件分派器,文件事件分派器接收到socket之后会根据socket产生的事件类型调用对应的事件处理器进行处理。
文件事件处理器分为几种:

连接应答处理器:用于处理客户端的连接请求;
命令请求处理器:用于执行客户端传递过来的命令,比如常见的set、lpush等;
命令回复处理器:用于返回客户端命令的执行结果,比如set、get等命令的结果;
事件种类:

AE_READABLE:与两个事件处理器结合使用。
当客户端连接服务器端时,服务器端会将连接应答处理器与socket的AE_READABLE事件关联起来;
当客户端向服务端发送命令的时候,服务器端将命令请求处理器与AE_READABLE事件关联起来;
AE_WRITABLE:当服务端有数据需要回传给客户端时,服务端将命令回复处理器与socket的AE_WRITABLE事件关联起来。
Redis的客户端与服务端的交互过程如下所示:

I/O 多路复用模块封装了底层的 select、epoll、avport 以及 kqueue 这些 I/O 多路复用函数;
因为 Redis 需要在多个平台上运行,同时为了最大化执行的效率与性能,所以会根据编译平台的不同选择不同的 I/O 多路复用函数作为子模块,提供给上层统一的接口;
Redis 会优先选择时间复杂度为 O(1) 的 I/O 多路复用函数作为底层实现,包括 Solaries 10 中的 evport、Linux 中的 epoll 和 macOS/FreeBSD 中的 kqueue,上述的这些函数都使用了内核内部的结构,并且能够服务几十万的文件描述符。

但是如果当前编译环境没有上述函数,就会选择 select 作为备选方案,由于其在使用时会扫描全部监听的描述符,所以其时间复杂度较差 O(n),并且只能同时服务 1024 个文件描述符,所以一般并不会以 select 作为第一方案使用。

🌹 🌹感谢大家,坚持看完,既然选择了这条路,那就一起加油,一起学习!如果需要学习资源,实战面试资料,项目资源。关注公众号:IT枫斗者,🌟根据关键字领取对应的资料福利🌟!咨询解决问题,公众号私聊枫哥,备注来意。

🍊回复:java全套学习资源

🍊回复:面试资料

🍊回复:枫哥简历

🍊回复:程序员表白神器               

(从此告别程序员单身狗!)

🍊回复:程序员兼职网站

🍊回复:枫哥666                             

( 获取66套项目实战资料,大厂面试视频)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT枫斗者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值