• Redis 单线程与多线程模型详解


    大家好 我是积极向上的湘锅锅💪💪💪

    IO模型 阻塞IO,非阻塞IO,IO多路复用,信号驱动IO,异步IO详解

    1. API

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    可以看做是一个接口的不同重载实现,也可以看作根据不同方案选择代码块
    以ae.select源码为例,值得关注的方法如下

    在这里插入图片描述
    不管是epoll_create还是epoll_ctl,想必不陌生,在上节已经讲的很详细
    有了对应的方法了,接下来就开始走流程(关键)


    2. 基于epoll的服务端流程

    这个可以做为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

    • 并且FD是ssfd,也就是serverSocket,那就证明有客户端要准备连接上服务端了,就会调用accept()接受客户端的socket,得到对应的客户端FD,最后需要调用epoll_ctl把客户端FD注册到rb_root红黑树上进行监听,后面新的客户端连接也是如此
    • FD不是ssfd,那就说明是客户端FD可读,那就读取请求参数进行业务处理,最后写出响应

    如果事件类型是EPOLLERR
    也就是各种异常,那就直接写出响应

    下图就更为清晰的阐述了用户空间和内核空间所做的一系列事情,更为直观,详细流程可以再根据这幅图再走一遍
    在这里插入图片描述


    3. Redis 线程模型

    前菜已去,该来的总会来

    Redis 内部使用文件事件处理器,这个文件事件处理器是单线程的,所以 Redis 才叫做单线程的模型

    基本流程简介:

    它采用 IO 多路复用机制同时监听多个socket,将产生事件的 socket 压入内存队列中,事件分派器根据 socket 上的事件类型来选择对应的事件处理器进行处理

    文件事件处理器的结构包含4个部分:
    ①多个socket;
    ②IO多路复用程序;
    ③文件事件分派器;
    ④事件处理器(命令请求处理器,命令回复处理器,连接应答处理器等等)

    以上的有个概念就好,怎么用的在下文

    Redis 线程模型流程

    1. 一开始,肯定需要服务端开启一个连接(调用epoll_create创建实例)等待客户端连接操作,所以就需要一个对应的处理器来处理连接,那就是连接应答处理器,什么时候用? go on reading

    注:图中的aeEventLoop相当于epoll_create
    在这里插入图片描述

    1. 创建好实例之后,就需要进行监听(图中的aeApiPoll就是epoll_wait)

    在这里插入图片描述

    根据箭头

    承接上图,在aeEventLoop之后,有个beforsleep,具体什么用看后文,然后再是调用aeApipoll(epoll_wait)进行监听
    如果serversoket有读事件,也就是在图的前面那三个蓝色圈圈 client Socket 要开始连接了,那就用连接应答处理器将client Socket的FD注册到我们创建的实例当中(红黑树添加节点)

    连接应答处理器:处理对象是serversoket,作用将客户端FD注册到实例当中

    1. 客户端FD注册之后,那就可能不只是服务端可读事件发生,而是可能就是客户端FD可读,那就需要命令请求处理器来处理

    在这里插入图片描述
    在我们的epoll网络模型中,这一步就应该是用户空间获取请求参数进行处理

    那在redis线程模型中,命令请求处理器所做的事情大致分为以下几类

    在这里插入图片描述

    • 每一个client都有属于自己的读缓冲区queryBuf,将请求数据写入client的的queryBuf缓冲区里面
    • 再调用函数将数据解析为redis命令
    • 执行redis命令
    • 将执行结果再次写入client写缓冲区buf里面,如果buf写不下,就会写到一个reply链表里面,理论上容量无上限
    • 将客户端添加到服务端定义好的队列里面去,这个队列就是存放那些被等待写出的客户端的,随着要处理的客户端越来越多,这个队列里面存放的待写出的客户端也就越来越多

    到这一步,仅仅是处理了数据,但是还没有真正的写出
    那什么时候写?
    注意前文的 beforsleep

    查看beforsleep源码

    在这里插入图片描述
    可以发现是遍历了队列里面的每一个client socket,监听FD的写事件,并且绑定了一个处理器,叫做 命令回复处理器

    命令回复处理器:将准备好的客户端缓冲区的数据取出,然后逐个写入到client socket里面

    在这里插入图片描述


    总结

    总的来说会监听服务端的读事件,同时也会监听客户端的读事件和写事件,分别由专门的处理器处理

    一共由三种事件

    • 服务端可读(连接应答处理器)
    • 客户端可读(命令请求处理器)
    • 客户端可写(命令回复处理器)

    根据其中的特点可以概括为IO多路复用+事件派发

    Redis单线程模型的瓶颈在哪里?
    redis执行命令也就是ms级别,而将数据放入缓存,也就是纯内存操作,也花不了太长时间,那真正可以使用多线程优化的地方时哪里?
    就是网络IO,IO在很多地方都有体现,数据库需要IO从磁盘读取数据,而这里就是网络IO,主要体现在俩个地方

    • 在处理客户端socket的读事件时,要从客户端socket读取数据并且解析为命令,这个操作涉及网络IO
    • 当从队列取出数据的时候,又要涉及到从服务端socket写数据到客户端socket,也涉及网络IO

    因此,在Redis 6.0 版本中引入了多线程,目的就是为了提高IO读写效率,因此在解析客户端命令,写响应结果时采用了多线程,而核心命令的执行,IO多路复用的模块还是单线程

    整个Redis线程模型就是如下过程

    在这里插入图片描述

    整个流程就结束了,建议拿自己慢慢画出来,熟练掌握之后,也就可以去跟面试官吹逼了

  • 相关阅读:
    职场经验:为什么要学习自动化测试?过来人告诉你答案
    怎样用读写锁快速实现一个缓存?
    微服务架构
    牛客 NC25005 [USACO 2008 Ope S]Clear And Present Danger
    Java输入输出之文件字符IO流之文件加密
    [基础服务] windows10安装WSL2
    Linux系统编程(五):信号
    「C系列」C 文件读写
    Multi Label Classification with Missing Labels(MLML)的几种loss设计
    学习GTEx数据库
  • 原文地址:https://blog.csdn.net/qq_56263094/article/details/126050324