大家好 我是积极向上的湘锅锅💪💪💪
IO模型 阻塞IO,非阻塞IO,IO多路复用,信号驱动IO,异步IO详解
Redis也通过IO多路复用提高性能,并且支持了各种不同的复用实现,在上节中select,poll,epoll模式中,是在linux的默认实现方案,但是不同的操作系统实现方案还有所不同,主要是以下几种:
可以发现都是ae开头,后面四个都是不同的实现方案,根据ae.c中的源码可以发现不同的操作系统选择的方案也不同
/* Include the best multiplexing layer supported by this system.
* The following should be ordered by performances, descending. */
#ifdef HAVE_EVPORT
#include "ae_evport.c"
#else
#ifdef HAVE_EPOLL
#include "ae_epoll.c"
#else
#ifdef HAVE_KQUEUE
#include "ae_kqueue.c"
#else
#include "ae_select.c"
#endif
#endif
#endif
可以看做是一个接口的不同重载实现,也可以看作根据不同方案选择代码块
以ae.select源码为例,值得关注的方法如下
不管是epoll_create还是epoll_ctl,想必不陌生,在上节已经讲的很详细
有了对应的方法了,接下来就开始走流程(关键)
这个可以做为redis线程模型的前菜,因为这个流程跟redis的很像,也算是各种实现方案的老前辈了,把这个理解透再去看redis的就要轻松很多,不废话直接上图
根据箭头
首先epoll_create在我们内核缓冲区创建实例,一个是红黑树rb_root,用来监听记录的FD,一个是链表list_head,用来记录就绪的FD
创建serverSocket文件,得到FD,然后调用epoll_ctl去注册到rb_root红黑树中,同时使用回调函数,一旦FD就绪,就会记录到list_head链表里
最后根据epoll_wait函数来等待数据就绪,首先判断链表是否为空,不为空就将对应链表的FD拷贝进空数组里面,然后让用户空间去遍历数组就可得到就绪的FD
为空就一直等待,直到有FD就绪或者超时,如果FD超时还未就绪,只能返回一个0,告诉用户进程没有就绪,然后重新调用epoll_wait,直到FD就绪
这里的一个基本流程就是epoll的流程,不过FD已经是对应serverSocket,实际是对应的那个端口号创建的,默认是6379
那什么情况serverSocket才能就绪?
有服务端端口等待连接,就有客户端请求连接,所以serverSocket只有一种情况才能就绪,那就是有客户端进行连接,事件的类型为读事件
接下来就需要根据返回的FD数组中的事件类型进行数据的处理
跟上图就区别的地方就是多了下半部分,所以只需要关注下半部分即可
从判断事件开始
如果事件类型是EPOLLIN
如果事件类型是EPOLLERR
也就是各种异常,那就直接写出响应
下图就更为清晰的阐述了用户空间和内核空间所做的一系列事情,更为直观,详细流程可以再根据这幅图再走一遍
前菜已去,该来的总会来
Redis 内部使用文件事件处理器,这个文件事件处理器是单线程的,所以 Redis 才叫做单线程的模型
基本流程简介:
它采用 IO 多路复用机制同时监听多个socket,将产生事件的 socket 压入内存队列中,事件分派器根据 socket 上的事件类型来选择对应的事件处理器进行处理
文件事件处理器的结构包含4个部分:
①多个socket;
②IO多路复用程序;
③文件事件分派器;
④事件处理器(命令请求处理器,命令回复处理器,连接应答处理器等等)
以上的有个概念就好,怎么用的在下文
注:图中的aeEventLoop相当于epoll_create
根据箭头
承接上图,在aeEventLoop之后,有个beforsleep,具体什么用看后文,然后再是调用aeApipoll(epoll_wait)进行监听
如果serversoket有读事件,也就是在图的前面那三个蓝色圈圈 client Socket 要开始连接了,那就用连接应答处理器将client Socket的FD注册到我们创建的实例当中(红黑树添加节点)
连接应答处理器:处理对象是serversoket,作用将客户端FD注册到实例当中
在我们的epoll网络模型中,这一步就应该是用户空间获取请求参数进行处理
那在redis线程模型中,命令请求处理器所做的事情大致分为以下几类
到这一步,仅仅是处理了数据,但是还没有真正的写出
那什么时候写?
注意前文的 beforsleep
查看beforsleep源码
可以发现是遍历了队列里面的每一个client socket,监听FD的写事件,并且绑定了一个处理器,叫做 命令回复处理器
命令回复处理器:将准备好的客户端缓冲区的数据取出,然后逐个写入到client socket里面
总的来说会监听服务端的读事件,同时也会监听客户端的读事件和写事件,分别由专门的处理器处理
一共由三种事件
根据其中的特点可以概括为IO多路复用+事件派发
Redis单线程模型的瓶颈在哪里?
redis执行命令也就是ms级别,而将数据放入缓存,也就是纯内存操作,也花不了太长时间,那真正可以使用多线程优化的地方时哪里?
就是网络IO,IO在很多地方都有体现,数据库需要IO从磁盘读取数据,而这里就是网络IO,主要体现在俩个地方
因此,在Redis 6.0 版本中引入了多线程,目的就是为了提高IO读写效率,因此在解析客户端命令,写响应结果时采用了多线程,而核心命令的执行,IO多路复用的模块还是单线程
整个Redis线程模型就是如下过程
整个流程就结束了,建议拿自己慢慢画出来,熟练掌握之后,也就可以去跟面试官吹逼了