redis IO多路复用机制

打印 上一主题 下一主题

主题 1882|帖子 1882|积分 5646

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x

目录
一、五种 I/O 模型
1.阻塞IO(Blocking IO)
2.非阻塞IO(Nonblocking IO)
3.IO多路复用(IO Multiplexing)
通知的方式
select模式
poll模式
epoll模式
4.信号驱动IO(Signal Driven IO)
5.异步IO(Asynchronous IO)
总结
二、Redis 
Redis为什么快?
Redis 线程模型
1.Redis6.0 之前为什么倒霉用多线程?
2.Redis6.0 之后为何引入了多线程?
3.Redis 背景线程相识吗?
为何Redis要利用I/O多路复用技术?
 IO多路复用原理
IO 多路复用的工作原理
文件变乱处置处罚器结构
Reactor设计模式
长处


Redis 是一个单线程却性能非常好的内存数据库, 重要用来作为缓存系统。 Redis 接纳网络 I/O 多路复用技术来包管在多个连接时,系统的高吞吐量(TPS)。
   系统吞吐量(TPS)指的是系统在单位时间内可处置处罚的变乱的数量,是用于衡量系统性能的重要指标。影响系统吞吐量的因素很多,包括并发数和系统资源(CPU、内存、系统I/O操纵、外部接口)等,系统资源等这些因素可以用均匀相应时间指标来衡量。
  
  Socket套接字:对网络中不同主机上的应用进程之间举行双向通信的端点的抽象。
例子:客户端在将数据通过网线发送到服务端的时候,客户端发送数据需要一个出口,
服务端接收数据需要一个入口,这两个“口子”就是Socket。
  
  文件描述符(File Descriptor):简称FD,是一个从0 开始的无符号整数,用来关联Linux中的一个文件。在Linux中,齐备皆文件,例如通例文件、视频、硬件设备等,固然也包括网络套接字(Socket)。
  一、五种 I/O 模型

要理解 Redis 接纳网络 I/O 多路复用技术,就得先相识五种 I/O 模型。
   一个小例子:你是一个老师,让学生做作业,学生做完作业后收作业。
1)同步阻塞:逐个收作业,先收A,再收B,接着是C、D,假如有一个学生还未做完,
则你会比及他写完,然后才继续收下一个。
2)同步非阻塞:逐个收作业,先收A,再收B,接着是C、D,假如有一个学生还未做完,
则你会跳过该学生,继续去收下一个。
3)
  selectpoll:学生写完了作业会举手,但是你不知道是谁举手,需要一个个的去询问。
epoll:学生写完了作业会举手,你知道是谁举手,你直接去收作业。
  1.阻塞IO(Blocking IO)

最传统的一种 I/O 模型,即在读写数据过程中会发生阻塞现象。当利用 read 或者 write 对某一个文件描述符(File Descriptor 以下简称 FD)举行读写时,假如当前 FD 不可读或不可写,整个 Redis 服务就不会对别的的操纵作出相应,导致整个服务不可用。
这也就是传统意义上的,也就是我们在编程中利用最多的阻塞模型。
具体流程:用户去读取数据时,会去先发起recvform一个下令,去尝试从内核上加载数据,假如内核没有数据,那么用户就会等待,此时内核会去从硬件上读取数据,内核读取数据之后,会把数据拷贝到用户态,并且返回ok,整个过程,都是阻塞等待的。
普通来说,当用户线程发出 I/O 请求之后,内核会去查看数据是否就绪,假如没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态(block),用户线程交出 CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才排除阻塞状态。data = socket.read();。假如数据没有就绪,用户线程就会一直阻塞在 read 方法。

阻塞模型固然开发中非常常见也非常易于理解,但是由于它会影响其他 FD 对应的服务,以是在需要处置处罚多个客户端任务的时候,往往都不会利用阻塞模型。 
2.非阻塞IO(Nonblocking IO)

具体流程:当用户线程发起一个 read 操纵后,并不需要等待,而是马上就得到了一个结果。假如结果是一个 error 时,它就知道数据还没有预备好,于是它可以再次发送 read 操纵。一旦内核中的数据预备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。
以是究竟上,在非阻塞 I/O 模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞 I/O 不会交出 CPU,而会一直占用 CPU,从而导致 CPU 占用率非常高。


3.IO多路复用(IO Multiplexing)

阻塞式的 I/O 模型并不能满意这里的需求,我们需要一种服从更高的 I/O 模型来支撑 Redis 的多个客户(redis-cli),这里涉及的就是 I/O 多路复用模型了。多路复用 I/O 模型是现在利用得比力多的 I/O 模型。
IO多路复用的表明,即利用一个或一组线程处置处罚多个tcp连接。


  • IO指代网络IO,需要举行模态转换的读写操纵
  • 多路,多个客户端连接(socket)
  • 复用,复用一个或多个线程 
IO多路复用是利用单个线程来同时监听多个FD,并在某个FD可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。
在多路复用 I/O 模型中,会有一个线程不断去轮询多个 socket 的状态,只有当 socket 真正有读写变乱时,才真正调用现实的 I/O 读写操纵。因为在多路复用 I/O 模型中,只需要利用一个线程就可以管理多个 socket,系统不需要创建新的进程或者线程,也不必维护这些线程和进程。并且只有在真正有 socket 读写变乱举行时,才会利用 I/O 资源,以是它大大减少了资源占用(如 CPU)。 

当如下任一环境发生时,会产生套接字的可读变乱:


  • 该套接字的接收缓冲区中的数据字节数大于等于套接字接收缓冲区低水位标记的巨细;
  • 该套接字的读半部关闭(也就是收到了FIN),对这样的套接字的读操纵将返回0(也就是返回EOF);
  • 该套接字是一个监听套接字且已完成的连接数不为0;
  • 该套接字有错误待处置处罚,对这样的套接字的读操纵将返回-1。
当如下任一环境发生时,会产生套接字的可写变乱:


  • 该套接字的发送缓冲区中的可用空间字节数大于等于套接字发送缓冲区低水位标记的巨细;
  • 该套接字的写半部关闭,继续写会产生SIGPIPE信号;
  • 非阻塞模式下,connect返回之后,该套接字连接成功或失败;
  • 该套接字有错误待处置处罚,对这样的套接字的写操纵将返回-1。
   会思考的你大概会想到,可以接纳多线程+ 阻塞 I/O 到达类似的效果,但是由于在多线程 + 阻塞 I/O 中,每个 socket对应一个线程,这样会造成很大的资源占用,并且尤其是对于长连接来说,线程的资源一直不会释放,假如后面连续有很多连接的话,就会造成性能上的瓶颈。
  而多路复用 I/O 模式,通过一个线程就可以管理多个 socket,只有当 socket 真正有读写变乱发生才会占用资源来举行现实的读写操纵。因此,多路复用IO比力适合连接数比力多的环境。
  

具体流程:通过FD,我们的网络模型可以利用一个线程监听多个FD,并在某个FD可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。当用户去读取数据的时候,不再去直接调用recvfrom了,而是调用select的函数,select函数会将需要监听的数据交给内核,由内核去查抄这些数据是否就绪了。假如说这个数据就绪了,就会通知应用步伐数据就绪,然厥后读取数据,再从内核中把数据拷贝给用户态,完成数据处置处罚,假如N多个FD一个都没处置处罚完,此时就举行等待。
利用IO复用模式,可以确保去读数据的时候,数据是肯定存在的,它的服从比原来的阻塞IO和非阻塞IO性能都要高。
别的,多路复用 I/O 为何比非阻塞 I/O 模型的服从高?是因为在非阻塞 I/O 中,不断地询问 socket 状态时通过用户线程去举行的。而在多路复用 I/O 中,轮询每个 socket 状态是内核在举行的,这个服从要比用户线程要高的多。
值得留意的是,多路复用 I/O 模型是通过轮询的方式来检测是否有变乱到达,并且对到达的变乱逐一举行相应。因此对于多路复用 I/O 模型来说,一旦变乱相应太慢,那么就会导致后续的变乱迟迟得不到处置处罚,并且会影响新的变乱轮询。
这就是说,假如 Redis 每条下令实行假如占用大量时间,就会造成其他线程阻塞,对于 Redis 这种高性能服务是致命的,以是 Redis 是面向高速实行的数据库。
通知的方式

常见的 linux 提供的 I/O多路复用机制的实现方式有 select、poll、epoll 等,select 和 poll 都是基于轮询的方式去获取就绪连接,而 epoll 是基于变乱驱动的方式去获取就绪连接。
select模式

Select模型是利用select函数来监听多个文件描述符(包括网络连接),一旦有一个或多个文件描述符就绪,就会通知Redis举行相应的IO操纵。
简单说,就是我们把需要处置处罚的数据封装成FD,然后在用户态时创建一个FD的集合(这个集合的巨细是要监听的谁人FD的最大值+1,但是巨细整体是有限定的 ),这个集合的长度巨细是有限定的。同时在这个集合中,标明出来我们要控制哪些数据。
比如,要监听的数据,是1,2,5三个数据,此时会实行select函数,然后将整个fd发给内核态。内核态会去遍历用户态传递过来的数据,假如发现这里边都数据都没有就绪,就休眠,直到有数据预备好时,就会被叫醒。叫醒之后,再次遍历一遍,看看谁预备好了,然后再将处置处罚掉没有预备好的数据。末了再将这个FD集合写回到用户态中去,此时用户态就知道此时有人预备好了。但是对于用户态而言,并不知道谁处置处罚好了,以是用户态也需要去举行遍历,然后找到对应预备好数据的节点,再去发起读请求。
长处是:不需要每个FD都举行一次系统调用,办理了频仍的用户态内核态切换的问题。
我们会发现,这种模式固然比阻塞IO和非阻塞IO好,但是依然有些麻烦的事情, 比如说频仍的传递fd集合,频仍的去遍历FD等问题。因此,存在的三个问题:


  • 单进程监听的FD存在限定,最大不超过1024。
  • 每次select都需要把所有要监听的FD都拷贝到内核空间。
  • 不知道具体是哪个文件描述符就绪,每次都要遍历所有FD来判定就绪状态。
poll模式

Poll模型与Select类似,但性能更优。它利用poll函数来监听并处置处罚多个文件描述符。
poll模式针对select模式做了简单改进,


  • select模式中的fd_set巨细固定为1024,而pollfd在内核中接纳链表,理论上无上限。
  • 监听FD越多,每次遍历消耗时间也越久,性能反而会降落。
具体的IO流程:

  • 创建pollfd数组,向其中添加关注的fd信息,数组巨细自定义。
  • 调用poll函数,将pollfd数组拷贝到内核空间,转链表存储,无上限。
  • 内核遍历fd,判定是否就绪。
  • 数据就绪或超时后,拷贝pollfd数组到用户空间,返回就绪fd数量n。
  • 用户进程判定n是否大于0,大于0则遍历pollfd数组,找到就绪的fd。
存在的问题:poll利用链表办理了select中监听FD上限的问题,但依然要遍历所有FD,假如监听较多,性能会降落。
epoll模式

Epoll模型只实用于Linux系统,在高并发场景下性能最好。它利用epoll函数来监听多个文件描述符,并利用变乱驱动的方式处置处罚IO操纵。
epoll模式提供了三个函数:
1)eventpoll(),包括红黑树和一个链表。红黑树重要记录要监听的FD,链表重要记录就绪的FD。
2)epoll_ctl的作用是当你对一个新的fd的读/写变乱感爱好时,通过该调用将fd与相应的感爱好变乱更新到context中。
调用epoll_ctl()操纵,可以将要监听的数据添加到红黑树上去,并且给每个fd设置一个监听函数。这个函数会在fd数据就绪时触发,也就是预备好了,就会把fd把数据添加到list_head中去。
3)epoll_wait的作用是等待context中fd的变乱发生。
调用epoll_wait(),就去等待,在用户态创建一个空的events数组,当就绪之后,我们的回调函数会把数据添加到list_head中去,当调用这个函数的时候,会去查抄list_head,固然这个过程需要参考设置的等待时间,可以等肯定时间,也可以一直等, 假如在此过程中,查抄到了list_head中有数据会将数据添加到链表中,此时将数据放入到events数组中,并且返回对应的操纵的数量,用户态的此时收到相应后,从events中拿到对应预备好的数据的节点,再去调用方法去拿数据。
怎样办理select和poll存在的这些问题的?
epoll的办理方案不像select或poll一样每次都把current轮番加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,叫醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作现实上就是在这个就绪链表中查看有没有就绪的fd。


  • 基于epoll实例中的红黑树保存要监听的FD,理论上无上限,而且增删改查服从都非常高。
  • 每个FD只需要实行一次epoll_ctl添加到红黑树,以后每次epol_wait无需传递任何参数,无需重复拷贝FD到内核空间。
  • 利用ep_poll_callback机制来监听FD状态,无需遍历所有FD,因此性能不会随监听的FD数量增多而降落。
具体的IO过程如下:








epoll 其实只是众多实现 I/O多路复用模型的技术当中的一种而已,但是相比其他 I/O 多路复用技术(select, poll等),epoll有诸多长处(Redis 也支持 select 和 poll,默认利用 epoll):

  • epoll 没有最大并发连接的限定,上限是最大可以打开文件的数量,这个数字一般远大于 2048。
  • 服从提升:epoll 最大的长处就在于它只管你“活跃”的连接,而跟连接总数无关。因此在现实的网络环境中, epoll 的服从就会远远高于 select 和 poll。
  • 内存拷贝:epoll 直接利用的 "共享内存",可以跳过传统的内存拷贝操纵,服从更高。
 缺点:


  • 跨平台性不够好,只支持linux,macOs等操纵系统不支持。
  • 相较于 epoll,select 更轻量可移植性更强。
  • 在监听连接数和变乱较少的场景下,select大概更优。
 
4.信号驱动IO(Signal Driven IO)

在信号驱动 I/O 模型中,当用户线程发起一个 I/O 请求操纵,会给对应的 socket 注册一个信号函数,然后用户线程会继续实行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用 I/O 读写操纵来举行现实的 I/O 请求操纵。
这个一般用于 UDP 中,对 TCP 套接口险些是没用的,缘故原由是该信号产生得过于频仍,并且该信号的出现并没有告诉我们发生了什么事情。

5.异步IO(Asynchronous IO)

异步 I/O 模型才是最理想的 I/O 模型。
在异步 I/O 模型中,当用户线程发起 read 操纵之后,立刻就可以开始去做别的的事。而另一方面,从内核的角度,当它收到一个 asynchronous read 之后,它会立刻返回,说明 read 请求已经成功发起了,因此不会对用户线程产生任何阻塞(block)。
然后,内核会等待数据预备完成,然后将数据拷贝到用户线程。当这齐备都完成之后,内核会给用户线程发送一个信号,告诉它 read 操纵完成了。
也就是说,用户线程完全不需要关心现实的整个 I/O 操纵是怎样举行的,只需要先发起一个请求,当接收内核返回的成功信号时表示 I/O 操纵已经完成,可以直接去利用数据了。
这种方式,不仅仅是用户态在试图读取数据后不阻塞,而且当内核的数据预备完成后,也不会阻塞。

具体流程:它会由内核将所有数据处置处罚完成后,由内核将数据写入到用户态中,然后才算完成,以是性能极高,不会有任何阻塞,全部都由内核完成,可以看到,异步IO模型中,用户进程在两个阶段都黑白阻塞状态。
总结

这五种 I/O 模型中,前面四种 I/O 模型现实上都属于同步 I/O,只有末了一种是真正的异步 I/O。
因为无论是多路复用 I/O模型还是信号驱动 I/O 模型,I/O 操纵的第 2 个阶段都会引起用户线程阻塞,也就是内核举行数据拷贝的过程都会让用户线程阻塞。


二、Redis 

Redis为什么快?

Redis 之以是快速,重要有以下几个缘故原由:

  • 内存存储:Redis 将所有数据存储在内存中,内存的读写速度远快于磁盘,这使得 Redis 在处置处罚请求时可以或许迅速相应。
  • 单线程模型:Redis 利用单线程处置处罚请求,避免了多线程中的上下文切换和锁竞争问题。固然看似单线程大概导致性能瓶颈,但 Redis 的变乱驱动模型和高效的 I/O 操纵使得它在处置处罚大量请求时仍然能保持高性能。
  • 高效的数据结构:Redis 提供了多种高效的数据结构(如字符串、哈希、列表、集合、有序集合等),这些数据结构针对特定操纵举行了优化,能快速处置处罚不同类型的数据请求。
  • 长期化机制:固然 Redis 是内存数据库,但它提供了快照和 AOF(Append Only File)两种长期化机制,可以在确保数据长期化的同时,保持高性能。
  • 客户端缓存:Redis 支持发布/订阅模式,可以或许有用减少重复请求和不必要的数据传输,进一步提高相应速度。
  • 高效的网络协议:Redis 利用自定义的二进制协议,减少了网络传输时的数据包巨细息争析时间,提高了网络 I/O 的服从。
  • 丰富的下令和功能:Redis 提供了丰富的内置下令,针对常用操纵举行了优化,减少了开发者在应用层的处置处罚时间。
通过以上这些机制,Redis 可以或许在处置处罚大规模数据时保持高效和快速的性能体现。
Redis 线程模型

Redis 基于 Reactor 模式设计开发了一套高效的变乱处置处罚模型 (Netty 的线程模型也基于 Reactor 模式,Reactor 模式不愧是高性能 IO 的基石),这套变乱处置处罚模型对应的是 Redis 中的文件变乱处置处罚器(file event handler)。由于文件变乱处置处罚器是单线程方式运行的,以是我们一般都说 Redis 是单线程模型。


  • 文件变乱处置处罚器利用 I/O 多路复用步伐来同时监听多个套接字,并根据套接字现在实行的任务来为套接字关联不同的变乱处置处罚器。
  • 当被监听的套接字预备好实行连策应答(accept)、读取(read)、写入(write)、关闭(close)等操纵时,与操纵相对应的文件变乱就会产生,这时文件变乱处置处罚器就会调用套接字之前关联好的变乱处置处罚器来处置处罚这些变乱。
长处:固然文件变乱处置处罚器以单线程方式运行,但通过利用 I/O 多路复用步伐来监听多个套接字,文件变乱处置处罚器既实现了高性能的网络通信模型,又可以很好地与 Redis 服务器中其他同样以单线程方式运行的模块举行对接,这保持了 Redis 内部单线程设计的简单性。
组成:
(1) 多个 socket(客户端连接)
(2)IO 多路复用步伐(支持多个客户端连接的关键)
既然是单线程,那怎么监听大量的客户端连接呢?Redis 通过 IO 多路复用步伐监听来自客户端的多个套接字,并向文件变乱派发器传递那些产生了变乱的套接字。
只管多个文件变乱大概会并发地出现,但I/O多路复用步伐总是会将所有产生的套接字都放到同一个队列(aeEventLoop的fired就绪变乱表)里,然后文件变乱处置处罚器会以有序、同步、单个套接字的方式处置处罚该队列中的套接字,也就是处置处罚就绪的文件变乱。
好处:I/O 多路复用技术的利用让 Redis 不需要额外创建多余的线程来监听客户端的大量连接,低落了资源的消耗(和 NIO 中的 Selector 组件很像)。
(3)文件变乱分派器(将 socket 关联到相应的变乱处置处罚器)
(4) 变乱处置处罚器(连策应答处置处罚器、下令请求处置处罚器、下令回复处置处罚器)

以是,一次 Redis 客户端与服务器举行连接并且发送死令的过程:


  • 客户端向服务端发起创建 socket 连接的请求,那么监听套接字将产生 AE_READABLE 变乱,触发连策应答处置处罚器实行。处置处罚器会对客户端的连接请求举行应答,然后创建客户端套接字,以及客户端状态,并将客户端套接字的 AE_READABLE 变乱与下令请求处置处罚器关联。
  • 客户端创建连接后,向服务器发送死令,那么客户端套接字将产生 AE_READABLE 变乱,触发下令请求处置处罚器实行,处置处罚器读取客户端下令,然后传递给相干步伐去实行。
  • 实行下令得到相应的下令回复,为了将下令回复传递给客户端,服务器将客户端套接字的 AE_WRITEABLE 变乱与下令回复处置处罚器关联。当客户端试图读取下令回复时,客户端套接字产生 AE_WRITEABLE 变乱,触发下令回复处置处罚器将下令回复全部写入到套接字中。
1.Redis6.0 之前为什么倒霉用多线程?

固然说 Redis 是单线程模型,但现实上,Redis 在 4.0 之后的版本中就已经加入了对多线程的支持。
不过,Redis 4.0 增加的多线程重要是针对一些大键值对的删除操纵的下令,利用这些下令就会利用主线程之外的其他线程来“异步处置处罚”,从而减少对主线程的影响。
为此,Redis 4.0 之后新增了几个异步下令:


  • UNLINK:可以看作是 DEL 下令的异步版本。
  • FLUSHALL ASYNC:用于清空所有数据库的所有键,不限于当前 SELECT 的数据库。
  • FLUSHDB ASYNC:用于清空当前 SELECT 数据库中的所有键。
总的来说,直到 Redis 6.0 之前,Redis 的重要操纵仍然是单线程处置处罚的。
那 Redis6.0 之前为什么倒霉用多线程? 我觉得重要缘故原由有 3 点:


  • 单线程编程容易并且更容易维护;
  • Redis 的性能瓶颈不在 CPU ,重要在内存和网络;
  • 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。
2.Redis6.0 之后为何引入了多线程?

Redis6.0 引入多线程重要是为了提高网络 IO 读写性能,因为这个算是 Redis 中的一个性能瓶颈(Redis 的瓶颈重要受限于内存和网络)。
我们可以在 Redis 在中利用 DEL 下令来删除一个键对应的值,假如待删除的键值对占用了较小的内存空间,那么哪怕是同步地删除这些键值对也不会消耗太多的时间。
但是对于 Redis 中的一些超大键值对,几十 MB 或者几百 MB 的数据并不能在几毫秒的时间内处置处罚完,Redis 大概会需要在释放内存空间上消耗较多的时间,这些操纵就会阻塞待处置处罚的任务,影响 Redis 服务处置处罚请求的 PCT99 和可用性。
固然,Redis6.0 引入了多线程,但是 Redis 的多线程只是在网络数据的读写这类耗时操纵上利用了,实行下令仍然是单线程次序实行。因此,你也不需要担心线程安全问题。
Redis6.0 的多线程默认是禁用的,只利用主线程。


  • 如需开启需要设置 IO 线程数 > 1,需要修改 redis 设置文件 redis.conf。




    • io-threads 的个数一旦设置,不能通过 config 动态设置。
    • 当设置 ssl 后,io-threads 将不工作。



  • 开启多线程后,默认只会利用多线程举行 IO 写入 writes,即发送数据给客户端。假如需要开启多线程 IO 读取 reads,同样需要修改 redis 设置文件 redis.conf。
  1. io-threads 4 #设置1的话只会开启主线程,官网建议4核的机器建议设置为2或3个线程,8核的建议设置为6个线程
  2. io-threads-do-reads yes
复制代码
3.Redis 背景线程相识吗?

固然常常说 Redis 是单线程模型(重要逻辑是单线程完成的),但现实另有一些背景线程用于实行一些比力耗时的操纵:


  • 通过 bio_close_file 背景线程来释放 AOF / RDB 等过程中产生的临时文件资源。
  • 通过 bio_aof_fsync 背景线程调用 fsync 函数将系统内核缓冲区还未同步到到磁盘的数据强制刷到磁盘( AOF 文件)。
  • 通过 bio_lazy_free背景线程释放大对象(已删除)占用的内存空间。

为何Redis要利用I/O多路复用技术?

Redis 是单线程架构,所有的下令操纵都是先进入队列,然后一个一个按照次序线性实行的。也就是说Rdis是跑在单线程中的,所有的操纵都是按照次序线性实行的。但是由于读写操纵等待用户输入或输出都是阻塞的,以是 I/O 操纵在一般环境下往往不能直接返回,这会导致某一文件的 I/O 阻塞导致整个进程无法对别的客户提供服务,而接纳 I/O 多路复用技术就是为了办理这个问题。
一句话总结就是,Redis 接纳 I/O 多路复用技术(epoll)是因为 Redis 是单线程架构,是为了避免网络 I/O 读写操纵阻塞整个进程。
那Redis 为何不接纳异步 I/O 模型,这个不是服从更高吗?这玩意儿在多线程下才气发挥功效,而 Redis 是单线程架构。
 IO多路复用原理

所谓I/O多路复用机制,就是说通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或写就绪),可以或许通知步伐举行相应的读写操纵。
I/O多路复用机制的核心思想是让单个线程去监控多个连接,一旦连接就绪,也就是触发了读/写变乱的时候,就会去通知对应的应用步伐去主动获取这个就绪的连接举行读写操纵。也就是在应用步伐里面可以利用单个线程同时行止理多个客户端连接。在对系统资源消耗比力小的环境下,去提升服务端的连接处置处罚数量。
客户端请求到服务端里面的时候,此时客户端在传输数据的过程中,为了避免服务端在 read 客户端数据的时候阻塞,服务端会先把请求注册到复路器 selector 中,服务端此时不需要等待,只需要启动一个线程,通过 selector.select() 方法去阻塞轮询复路器上就绪的 channel 就可以了。也就是说假如某个客户端的连接的数据传输完成之后,那么 select() 方法归去返回这个就绪的 channel,然后实行相干的处置处罚逻辑。
常见的 linux 提供的 I/O多路复用机制的实现方式有 select、poll、epoll 等,select 和 poll 都是基于轮询的方式去获取就绪连接,而 epoll 是基于变乱驱动的方式去获取就绪连接。以是从性能来看,epoll 优于select 和 poll。这种机制的利用需要select、pol川、epol来配合。
Redis利用epoll来实现IO多路复用,将连接信息和变乱放到队列中,依次放到文件变乱分派器,变乱分派器将变乱分发给变乱处置处罚器。


IO 多路复用的工作原理

IO 多路复用的工作原理如下:


  • 变乱注册:当新的客户端连接创建时,Redis 会为这个连接创建一个套接字,并将其对应的文件描述符注册到 IO 多路复用步伐中,通常利用的是 epoll(在 Linux 系统中)或其他平台的类似机制(如 kqueue、evport)。
  • 变乱监听:IO 多路复用步伐会持续监控所有注册的文件描述符,等待读、写、连接等变乱的发生。这一过程黑白阻塞的,即它不会因为某个描述符未就绪而阻塞整个监控过程。
  • 变乱分发:一旦有变乱预备就绪(如客户端发送了数据,可以读取;或者服务器预备好发送数据到客户端,可以写入),IO 多路复用步伐会将该文件描述符标记为活跃状态,并通过变乱队列告知文件变乱分派器。
  • 变乱处置处罚:文件变乱分派器根据变乱类型和关联的变乱处置处罚器,分发控制权给对应的处置处罚器实行现实操纵,如读取数据、写入相应、处置处罚新连接请求或关闭连接等。
下图就是基于多路复用的redis IO模型。图中的多个FD就是刚才所说的多个套接字。Redis网络框架调用epoll机制,让内核监听这些套接字。此时Redis线程不会阻塞在某一个特定的监听或者已连接套接字上,也就是说,不会阻塞在某一个特定的客户端请求处置处罚上。正因为此,redis可以同时和多个客户端连接并处置处罚请求,从而提升并发性。

为了在请求到达时可以或许通知到redis线程,select/epoll提供了基于变乱的回调机制,即针对不同变乱的发生,调用相应的处置处罚函数。
那么,回调机制是怎么工作的?其实select/epoll一旦监听到FD上有请求到达时,就会触发相应的变乱。这些变乱会被放进一个变乱队列,redis单线程对该变乱队列不断举行处置处罚。这样一来,redis无需一直轮询是否有请求现实发生,这就可以避免造成CPU资源浪费。同时redis在对变乱队列中的变乱举行处置处罚时,会调用相应处置处罚函数。这就实现了基于变乱的回调。因为redis一直在对时间队列举行处置处罚,以是可以或许实时相应客户端请求,提升redis的相应性能。
普通的说,当我们的客户端想要去连接我们服务器,会去先到IO多路复用模型去举行列队,会有一个连策应答处置处罚器去担当读请求,然后又把读请求注册到具体模型中去。此时这些创建起来的连接,假如是客户端请求处置处罚器去举行实行下令时,它会去把数据读取出来,然后把数据放入到client中。clinet去解析当前的下令转化为redis熟悉的下令,接下来就开始处置处罚这些下令,从redis中的command中找到这些下令,然后就真正的去操纵对应的数据了。当数据操纵完成后,会去找到下令回复处置处罚器,再由它将数据写出。

文件变乱处置处罚器结构

Redis 的文件变乱处置处罚器重要包括以下几个组成部门:


  • Socket:用于客户端与服务端的网络通信。
  • IO 多路复用步伐:核心组件,负责监控多个文件描述符(FD,包括套接字描述符)的读写变乱。
  • 文件变乱分派器:接收 IO 多路复用步伐的通知,根据变乱类型调度相应的变乱处置处罚器。
  • 变乱处置处罚器:实行具体的读、写、连策应答、关闭等操纵。
Reactor设计模式

Redis 服务接纳 Reactor 的方式来实现文件变乱处置处罚器(每一个网络连接其实都对应一个文件描述符)。
文件变乱处置处罚器利用 I/O 多路复用模块同时监听多个 FD,当 accept、read、write 和 close 文件变乱产生时,文件变乱处置处罚器就会回调 FD 绑定的变乱处置处罚器。
固然整个文件变乱处置处罚器是在单线程上运行的,但是通过 I/O 多路复用模块的引入,实现了同时对多个 FD 读写的监控,提高了网络通信模型的性能,同时也可以包管整个 Redis 服务实现的简单。
长处

高并发处置处罚本事:通过单线程模型配合 IO 多路复用,Redis 可以或许高效地处置处罚成千上万个并发连接,避免了线程上下文切换的开销。
资源服从:由于所有操纵在一个线程内完成,减少了线程间的竞争和同步开销。
可扩展性和可移植性:Redis 实现了针对不同平台的 IO 多路复用库的抽象,使得其底层实现可以根据操纵系统特性灵活选择,包管了代码的可移植性。
需要留意的是,Redis IO多路复用是一种异步的IO模型,实用于处置处罚大量的小数据传输。在有大量的大数据传输的环境下,IO多路复用大概并不能得到很好的性能提升。此时可以考虑利用其他更适合的IO模型,如多线程或异步IO等。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

宝塔山

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表