百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

字节面试「NIO篇」——Reactor 模式就一定意味着高性能吗?

lipiwang 2024-11-26 06:04 7 浏览 0 评论

为什么是 Reactor

一般所有的网络服务,一般分为如下几个步骤:

  • 读请求(read request)
  • 读解析(read decode)
  • 处理程序(process service)
  • 应答编码 (encode reply)
  • 发送应答(send reply)

接下来,大明哥就来分析解决这个问题的最佳实践。

单线程模式

对于很多小伙伴来说,最简单,最传统的方式就是一个方法来处理所有的请求,这种实现方式最简单,也是最保险的方式。

这种方式实现起来虽然简单,但是性能不行,如果其中有一个请求因为某种原因阻塞了,则他后面的所有请求都会阻塞在那里,同时他也没法利用多 CPU 的性能,性能严重不足。

多线程模式

单线程的性能肯定不行,那就调整为多线程方式。

每来一个请求就会创建一个线程来处理,这种方式虽然不会像 单线程模式 一样,一个线程会阻塞所有的请求,但是他依然很大的问题:

  • 当客户端多,并发大的时候,需要创建大量线程来处理,线程的创建和销毁也很消耗资源,会导致整个系统的的资源占用较大
  • 同样无法应对高性能和高并发

线程池模式

既然多线程模式需要创建这么多线程,那么我们控制创建线程的个数,采用资源复用 线程池 的方式,也就是我们不需要再为每一个连接创建一个线程,而是创建一个线程池,将连接分配给线程,然后一个线程可以处理多个链接。

这种线程池的方式虽然解决了系统资源占用的问题,但是他依然带了了一个新的问题,每一个线程如何高效地处理请求呢?在上篇文章中 【死磕NIO】— 阻塞IO,非阻塞IO,IO复用,信号驱动IO,异步IO,这你真的分的清楚吗?我们提到过在单个线程中如果当前连接在进行read操作时,如果没有数据可读,则会发生阻塞,那么线程就没有办法继续处理其他连接的业务了。那么怎么解决?将 read 操作改为非阻塞的方式,既然改为了非阻塞方式,那线程如何知道read 操作有数据可读了呢?

  • 第一种方式,则是不断的去轮询,但是轮询要消耗 CPU的,而且随着轮询的线程多了,轮询的效率会越来越低
  • 第二种方式,事件驱动。当线程关心的事件发生了,比如read 有数据可读了,则通知相对应的线程进行处理

Reactor 模式

第二种方式就是 I/O多路复用。I/O多路复用就是通过一种机制,一个线程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知线程进行相应的读写操作。目前支持 IO多路复用技术有:

  • Linux:select、poll、epoll
  • MAC:kqueue
  • Windows:select

监听线程帮助我们监听哪些线程的事件已发生,发生后则通知相对应的线程进行处理,这样就可以避免进行很多无用的操作。对处理线程而言,整个处理过程只有调用 select、poll、epoll 的时候才会阻塞,其他时段,他可以处理其他的事情,这样整个线程会被充分利用起来,这样就高效很多了。

什么是 Reactor模式

上面讲了 Reactor 模式的演变,那什么是 Reactor 模式呢?

wiki上是这样定义的:

Reactor 模式也叫做反应器设计模式,它是一种为处理服务请求并发提交到一个或者多个服务处理程序的事件设计模式。当请求抵达后,服务处理程序使用解多路分配策略,然后同步地派发这些请求至相关的请求处理程序。

简要概括就是: 将消息放到了一个队列中,通过异步线程池对其进行消费。暂时理解成下面这个样子:

对于Reactor模式来说,他并没队列,每当有一个 Event 输入到 Server端时,Service Handler 会将其转发(dispatch)相对应的handler进行处理。

Reactor的组件主要包括三个:

  • Reactor:派发器,将 client端的事件分发给相对应的Handler
  • Acceptor:请求连接器,Reactor 接收到 client 连接事件后,会将其转发给 Acceptor,Acceptor 则会接受 Client 的连接,建立对应的Handler,并向 Reactor注册此Handler
  • Handler:请求处理器,负责事件的处理。

模型大致如下图:

Reactor 模式

Reactor 模型中的Reactor可以是多个也可以是单个,Handler同样可以是单线程也可以是多线程,所以组合的模式大致有如下四种:

  • 单Reactor单线程/进程
  • 单Reactor多线程/进程
  • 多Reactor单线程/进程
  • 多Reactor多线程/进程

其中第三种多Reactor单线程并没有什么实际的意思,所以大明哥重点介绍第一、二、四种。

单Reactor单线程/进程

  • Reactor 线程通过 select (IO多路复用接口)监听事件,收到事件后通过Dispatch 来分发事件,事件会分发给Acceptor和Handler 两个组件,具体是哪个组件要看事件的类型。
  • 如果事件类型为建立连接,则将事件分发给Acceptor,Acceptor会通过 accept 方法 获取连接,并创建一个 Handler 对象来处理后续的响应事件。
  • 如果时间类型不是建立连接,则将该事件交由当前连接的Handler来处理。

优缺点

  • 优点:该模型是将所有处理逻辑放在一个线程中实现,模型简单,没有多线程、进程通信、竞争的问题
  • 缺点由于只有一个线程,无法充分利用CPU,性能堪忧。同时Handler 在处理某个连接上的业务时,整个进程无法处理其他连接事件,很容易导致性能瓶颈。还有一个比较严重的可靠性问题,如果线程意外终止,或者进入死循环,则会导致整个线程都无法接受和处理事件了,造成节点故障。

单Reactor多线程/进程

单线程存在性能瓶颈,那我们就引入多线程方案。

Reactor 接受请求后,根据请求类型来进行分发,分发逻辑与 单Reactor单线程 模型一样,不同之处在于Handler不在进行业务处理了,它只负责接受和发送,Handler接受数据后,会将数据发送给 Worker 线程池中的线程处理,该线程才是处理业务的真正线程,线程将业务处理完成后,将数据发送给Handler,然后Handler 再send出去。

优缺点

  • 优点:由于Handler使用了多线程模式,则可以利用充分利用CPU的性能
  • 缺点:Handler使用多线程模式,则会涉及到数据共享的问题,需要考虑互斥,实现肯定比 单Reactor单线程模式复杂一些单Reactor,一个线程处理事件监听、分发、响应,对于高并发场景,容易造成性能瓶颈

多Reactor多线程/进程

单Reactor多线程模式解决了Handler单线程的性能问题,但是Reactor还是单线程的,对于高并发场景还是会有性能瓶颈,所以需要对Reactor调整为 多线程模式

  • 主线程中的MainReactor对象通过select监听事件,接收到事件后通过Dispatch进行分发,如果事件类型为建立连接则将事件分发给Acceptor 进行连接建立
  • 如果收到的事件不是连接,则他将事件分发个某个SubReactor,SubrReactor 将连接加入到连接队列进行监听,并创建Handler进行各种事件处理
  • 如果有新的事件发生,SubReactor 则会调用当前连接的Handler来进行处理。Handler 通过read 读取数据后,将数据发送给Worker线程进行处理,Worker线程池则会分配线程进行业务处理,处理完成后返回结果,Handler接受结果后,通过send发送给客户端

优缺点

  • 优点:该模式主线程和子线程分工明确,主线程只负责接收新连接,子线程负责完成后续的业务处理,同时主线程和子线程的交互也很简单,子线程接收主线程的连接后,只管业务处理即可,无须关注主线程
  • 缺点:模型复杂

这种模式适用于高并发场景,广泛运用于各种项目中,如大名鼎鼎的Netty。

Reactor 优缺点

Reactor模式有如下优点:

  • 响应快,不必为单个同步时间所阻塞
  • 可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销
  • 扩展性好,可以方便的通过增加 Reactor 实例个数来充分利用 CPU 资源
  • 复用性好,Reactor 模型本身与具体事件处理逻辑无关,具有很高的复用性

虽然Reactor有诸多优点,但是由于他的IO读写数据时还是在同一个线程中实现的,如果当前线程出现了一个长时间的IO数据读写,则会影响其他的client。那怎么解决呢?请静候下一篇文章。

原文:https://www.cnblogs.com/chenssy/p/15440348.html

相关推荐

linux实例之设置时区的方式有哪些

linux系统下的时间管理是一个复杂但精细的功能,而时区又是时间管理非常重要的一个辅助功能。时区解决了本地时间和UTC时间的差异,从而确保了linux系统下时间戳和时间的准确性和一致性。比如文件的时间...

Linux set命令用法(linux cp命令的用法)

Linux中的set命令用于设置或显示系统环境变量。1.设置环境变量:-setVAR=value:设置环境变量VAR的值为value。-exportVAR:将已设置的环境变量VAR导出,使其...

python环境怎么搭建?小白看完就会!简简单单

很多小伙伴安装了python不会搭建环境,看完这个你就会了Python可应用于多平台包括Linux和MacOSX。你可以通过终端窗口输入"python"命令来查看本地是否...

Linux环境下如何设置多个交叉编译工具链?

常见的Linux操作系统都可以通过包管理器安装交叉编译工具链,比如Ubuntu环境下使用如下命令安装gcc交叉编译器:sudoapt-getinstallgcc-arm-linux-gnueab...

JMeter环境变量配置技巧与注意事项

通过给JMeter配置环境变量,可以快捷的打开JMeter:打开终端。执行jmeter。配置环境变量的方法如下。Mac和Linux系统在~/.bashrc中加如下内容:export...

C/C++|头文件、源文件分开写的源起及作用

1C/C++编译模式通常,在一个C++程序中,只包含两类文件——.cpp文件和.h文件。其中,.cpp文件被称作C++源文件,里面放的都是C++的源代码;而.h文件则被称...

linux中内部变量,环境变量,用户变量的区别

unixshell的变量分类在Shell中有三种变量:内部变量,环境变量,用户变量。内部变量:系统提供,不用定义,不能修改环境变量:系统提供,不用定义,可以修改,可以利用export将用户变量转为环...

在Linux中输入一行命令后究竟发生了什么?

Linux,这个开源的操作系统巨人,以其强大的命令行界面而闻名。无论你是初学者还是经验丰富的系统管理员,理解在Linux终端输入一条命令并按下回车后发生的事情,都是掌握Linux核心的关键。从表面上看...

Nodejs安装、配置与快速入门(node. js安装)

Nodejs是现代JavaScript语言产生革命性变化的一个主要框架,它使得JavaScript从一门浏览器语言成为可以在服务器端运行、开发各种各样应用的通用语言。在不同的平台下,Nodejs的安装...

Ollama使用指南【超全版】(olaplex使用方法图解)

一、Ollama快速入门Ollama是一个用于在本地运行大型语言模型的工具,下面将介绍如何在不同操作系统上安装和使用Ollama。官网:https://ollama.comGithub:http...

linux移植(linux移植lvgl)

1uboot移植l移植linux之前需要先移植一个bootlader代码,主要用于启动linux内核,lLinux系统包括u-boot、内核、根文件系统(rootfs)l引导程序的主要作用将...

Linux日常小技巧参数优化(linux参数调优)

Linux系统参数优化可以让系统更加稳定、高效、安全,提高系统的性能和使用体验。下面列出一些常见的Linux系统参数优化示例,包括修改默认配置、网络等多方面。1.修改默认配置1.1修改默认编辑器默...

Linux系统编程—条件变量(linux 条件变量开销)

条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用。条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量可以通过允许线程阻塞和等...

面试题-Linux系统优化进阶学习(linux系统的优化)

一.基础必备优化:1.关闭SElinux2.FirewalldCenetOS7Iptables(C6)安全组(阿里云)3.网络管理服务||NetworkManager|network...

嵌入式Linux开发教程:Linux Shell

本章重点介绍Linux的常用操作和命令。在介绍命令之前,先对Linux的Shell进行了简单介绍,然后按照大多数用户的使用习惯,对各种操作和相关命令进行了分类介绍。对相关命令的介绍都力求通俗易懂,都给...

取消回复欢迎 发表评论: