Web体系结构之Reactor线程模型知识整理

本文介绍了Web请求处理体系结构,包括基于线程和事件驱动两种。阐述了I/O多路复用机制,如select、poll、epoll。重点讲解了Reactor响应式线程模型,包含单Reactor单线程、单Reactor多线程、主从多线程三种模型,分析其优缺点及消息处理流程,最后总结了Reactor模型的优点。

Web请求处理体系结构

  • thread-based architecture(基于线程)
    基于线程的体系结构通常会使用多线程来处理客户端的请求,每当接收到一个请求,便开启一个独立的线程来处理。

  • event-driven architecture(事件驱动)
    事件驱动体系结构是目前比较广泛使用的一种。这种方式会定义一系列的事件处理器来响应事件的发生,并且将服务端接受连接与对事件的处理分离。其中,事件是一种状态的改变。比如,TCP中socket的new incoming connection、ready for read、ready for write。
    Reactor%E5%8D%95%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B.png

I/O多路复用

I/O多路复用,I/O一般指的是网络I/O,多路指多个TCP连接(或多个Channel),复用指复用一个或少量线程。串起来理解就是多个网络I/O复用一个或少量的线程来处理这些连接。一般I/O多路复用机制都依赖于一个事件多路分离器(Event Demultiplexer),分离器对象可将来自事件源的I/O事件分离出来,并分发到对应的read/write事件处理器(Event Handler)。
目前支持I/O多路复用的系统调用模块select、poll、epoll,I/O多路复用是通过一个进程监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。select、pselect、poll、epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

select、poll、epoll简介

目前常用的I/O多路复用模型主要有 select、poll、epoll三种,epoll跟select函数都能提供多路I/O复用所需要的功能。在现在的Linux内核里有都能够支持,其中epoll是Linux所特有。在Linux 2.6版本之后才有,而select则应该是POSIX所规定,一般操作系统均有实现。

fd,全称file descriptor,即文件描述符。linux操作系统把一切的操作和资源都抽象成了文件,比如硬件设备、socket、磁盘、进程、线程等。在操作文件的时候,为了提高文件查找的效率,每个文件进程控制块中都有一份文件描述符表(可以看成是一个数组,里面的元素是指向file结构体指针类型),该数组的下表就是文件描述符,可理解成文件索引。

select

工作原理:

  1. 调用select()函数,拷贝内核fd
    int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);
  2. 监视select下的所有套接字,select监视到I/O事件发生后并不知道具体是哪几个流发生时间,所以无差别轮询所有流,找出能读出或写入数据的流进行操作。

select()监视包含多个文件描述符的数组,当select()返回后,这个数组中就绪的文件描述符会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理,因而带来了以下的缺点:

  • 每次调用都需要在内核遍历所有fd_set直到找到活跃的Socket,当fd_set很大时,遍历开销也很大
  • 为了减少数据拷贝带来的性能损坏,内核对被监控的fd_set集合大小做了限制(32位为1024,64位为2048),并且这个是通过宏控制的,不可被改变
  • 需要维护一个用来存放大量fd的数据结构,使得用户空间和内核空间在传递该结构时开销大

poll

poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll基于链表来存储,没有最大文件描述符数量的限制。即poll只解决了select fd_set的大小限制问题,但没有解决性能问题。

epoll

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
epoll有**水平触发-EPOLLLT(Level Triggered)和边缘触发-EPOLLET(Edge Triggered)**两种触发模式,LT是默认的模式,ET是“高速”模式,使用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

  • 水平触发(LT):默认工作模式,即当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序可以不立即处理该事件;下次调用epoll_wait时,会再次通知此事件
  • 边缘触发(ET): 当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次通知此事件。(直到你做了某些操作导致该描述符变成未就绪状态了,也就是说边缘触发只在状态由未就绪变为就绪时只通知一次)。

优点:

  • 没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口)
  • 效率提升,不像select每次都需要遍历,不会随着FD数目的增加效率下降,只有活跃可用的FD才会调用callback函数
  • 内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递,即epoll使用mmap减少复制开销。

Reactor(响应式)线程模型

Reactor设计模式是网络编程中的一种事件处理设计模式,用于处理一个或多个同时传递给服务处理程序的服务请求。然后,服务处理程序将传入的请求分解,并将它们同步地分派给相关的请求处理程序。Reactor模型被广泛用于基于I/O多路复用机制设计实现的软件编程中,如Netty、Redis都使用该模式解决高性能并发问题。
Reactor模型主要分为以下三个角色:

  • Reactor:将I/O事件分配给对应的handler处理
  • Acceptor:处理客户端连接事件
  • Handler:处理非阻塞的任务

Reactor设计模式是event-driven architecture的一种实现方式,是一种为处理并发服务请求,并将请求提交到一个或者多个服务处理程序的事件设计模式。
当客户端请求抵达后,服务处理程序使用多路分配策略,由一个非阻塞的线程来接收所有的请求,然后派发这些请求至相关的工作线程进行处理。 目前,许多流行的开源框架都用到了reactor模式,如:netty、node.js等,包括java的nio。常用的Reactor线程模型(Netty的三种线程模型)有3种:单Reactor单线程模型、单Reactor多线程模型、主从多线程模型。

单Reactor单线程模型

单Reactor单线程模型只是在代码上进行了组件的区分,但是整体操作还是单线程,不能充分利用硬件资源,handler业务处理部分没有异步。Redis使用单Reactor单进程的模型。
该模型的消息处理流程如下:

  1. Reactor线程通过select监控连接事件,接收事件后进行转发dispatch
  2. 如果Reactor接收到的事件是建立连接事件,则由acceptor接受连接,并创建handler处理后续事件
  3. 如果不是连接事件,则将事件交给相应的事件处理器Handler来响应
  4. handler会完成read->业务处理->send的完整业务流程

缺点:

  1. 单线程模型不能充分利用多核资源,所以实际使用的不多。
  2. 当处理读写任务线程(单线程)负载过高后,处理速度下降,事件会堆积,严重的会超时,可能导致客户端重新发送请求,性能越来越差
  3. 单线程可靠性问题,一旦Reactor线程意外中断会导致整个通信模块不可用,无法接收和处理外部消息,造成节点故障

Reactor%E5%8D%95%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B.png

单Reactor多线程模型

与单线程模型的区别在于事件处理器Handler部分采用了多线程,特点:

  • 有专门一个NIO线程(Acceptor线程)用于监听服务端,接收客户端的TCP连接请求
  • 网络I/O操作(读、写等)由一个NIO线程池负责,线程池可以采用标准的JDK线程池实现,它包含一个任务队列和N个可用的线程,由这些NIO线程负责消息的读取、解码、编码和发送

该模型的消息处理流程如下:

  1. Reactor对象通过Select监控客户端请求事件,将接收到的事件分发到相应的处理器
  2. 如果Reactor接收到的事件是建立连接事件,则由acceptor接受连接,并创建一个handler处理后续事件
  3. 如果不是连接事件,则将事件交给相应的事件处理器Handler处理
  4. Handler只负责响应事件,不做具体业务处理,通过Read读取数据后,会分发给后面的Worker线程池进行业务处理。
  5. Worker线程池会分配独立的线程进行业务处理,将响应结果发给Handler进行处理。
  6. Handler收到响应结果后通过send将响应结果返回给Client。

Reactor多线程模型可以满足绝大多数的场景,除了一些个别的特殊场景:比如一个NIO线程负责处理客户所有的连接请求,但是如果连接请求中包含认证的需求(安全认证),在百万级别的场景下,就存在性能问题了,因为认证本身就要消耗CPU。
single-reactor-multi-thread.jpeg

主从多线程模型

该模式将Reactor分成mainReactor和subReactor两部分,相应的职责如下:

  • mainReactor:负责监听server socket,处理网络I/O连接建立操作,将建立的socketChannel指定注册给subReactor
  • subReactor:主要做和建立起来的socket做数据交互和事件业务处理操作。一般情况下subReactor线程数上与CPU个数相同。

Nginx、Swoole、Memcached和Netty都是采用这种模型实现。该模型的消息处理流程如下:

  1. 从主线程池中随机选择一个Reactor线程作为acceptor线程,用于绑定监听端口,接收客户端连接
  2. acceptor线程接收客户端连接请求之后创建新的SocketChannel,将其注册到主线程池的其它Reactor线程上,由其负责接入认证、IP黑白名单过滤、握手等操作
  3. 步骤2完成之后,业务层的链路正式建立,将SocketChannel从主线程池的Reactor线程的多路复用器上摘除,重新注册到Sub线程池的线程上,并创建一个Handler用于处理各种连接事件
  4. 当有新的事件发生时,SubReactor会调用连接对应的Handler进行响应
  5. Handler通过Read读取数据后,会分发给后面的Worker线程池进行业务处理
  6. Worker线程池会分配独立的线程完成真正的业务处理,如何将响应结果发给Handler进行处理
  7. Handler收到响应结果后通过Send将响应结果返回给Client

服务端用于接收客户端连接的不再是个1个单独的NIO Reactor线程,而是由一个独立的NIO线程池Main Reactor Thread Pool与多个Sub Reactor Thread Pool·。Acceptor接收到客户端TCP连接请求处理完成后(可能包含接入认证等),将新创建的SocketChannel注册到IO线程池(sub reactor线程池)的某个IO线程上,由它负责SocketChannel的读写和编解码工作。Acceptor线程池仅仅只用于客户端的登陆、握手和安全认证,一旦链路建立成功,就将链路注册到后端subReactor线程池的IO线程上,由IO线程负责后续的IO操作。
multi-reactor-multi-thread.jpeg

  • Socket:套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。

抄自网络的三种Reactor模式的形象比喻

接待员相当于Reactor线程,服务员相当于处理线程

  1. 单Reactor单线程模型:接待员和服务员是同一个人,适合于客流量较少场景
  2. 单Reactor多线程模型:一个接待员、多个服务员,适合客流量大,接待员将顾客安排好桌子后,由桌子所负责的服务业进行服务,一般每个服务员负责一片区域中的多张桌子
  3. 主从Reactor多线程模型:多个接待员,多个服务员

总结

Reactor模型具有以下优点:

  • 响应快:虽然Reactor本身是同步的,但不会事件阻塞
  • 编程简单:可以最大程度的避免复杂的多线程及同步问题,避免了线程/进程切换的开销
  • 可扩展性:通过添加Reactor实例个数来充分利用CPU资源
  • 可复用性:Reactor模型本身与具体时间处理逻辑无关,具有很高的复用性
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值