IO模型及高性能网络架构分析

news2024/12/23 10:03:07

IO调用

操作系统一次IO过程: 应用程序发起的一次IO操作包含两个阶段:

  • IO调用:应用程序进程向操作系统内核发起调用。

  • IO执行:操作系统内核完成IO操作。

操作系统内核完成IO操作还包括两个过程:

  • 准备数据阶段:内核等待I/O设备准备好数据

  • 拷贝数据阶段:将数据从内核缓冲区拷贝到用户进程缓冲区

其实IO就是把进程的内部数据转移到外部设备,或者把外部设备的数据迁移到进程内部。外部设备一般指硬盘、socket通讯的网卡。一个完整的IO过程包括以下几个步骤:

  1. 应用程序进程向操作系统发起IO调用请求

  2. 操作系统准备数据,把IO外部设备的数据,加载到内核缓冲区

  3. 操作系统拷贝数据,即将内核缓冲区的数据,拷贝到用户进程缓冲区

五大IO模型

阻塞IO模型

假设应用程序的进程发起IO调用,但是如果内核的数据还没准备好的话,那应用程序进程就一直在阻塞等待,一直等到内核数据准备好了,从内核拷贝到用户空间,才返回成功提示,此次IO操作,称之为阻塞IO。

阻塞IO的缺点就是:如果内核数据一直没准备好,那用户进程将一直阻塞,浪费性能,可以使用非阻塞IO优化。

非阻塞IO模型

如果内核数据还没准备好,可以先返回错误信息给用户进程,让它不需要等待,而是通过轮询的方式再来请求。这就是非阻塞IO,流程图如下:

非阻塞IO的流程如下:

  • 应用进程向操作系统内核,发起recvfrom读取数据。

  • 操作系统内核数据没有准备好,立即返回EWOULDBLOCK错误码(就是EAGAIN)。

  • 应用程序进程轮询调用,继续向操作系统内核发起recvfrom读取数据。

  • 操作系统内核数据准备好了,从内核缓冲区拷贝到用户空间。

  • 完成调用,返回成功提示。

非阻塞IO模型,简称NIO(Non-Blocking IO)。它相对于阻塞IO,虽然大幅提升了性能,但是它依然存在性能问题,即频繁的轮询,导致频繁的系统调用,同样会消耗大量的CPU资源。可以考虑IO复用模型,去解决这个问题。

IO多路复用模型

既然NIO无效的轮询会导致CPU资源消耗,我们等到内核数据准备好了,主动通知应用进程再去进行系统调用,那不就好了嘛?

在这之前,我们先来复习下,什么是文件描述符fd(File Descriptor),它是计算机科学中的一个术语,形式上是一个非负整数。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。

IO复用模型核心思路:系统给我们提供一类函数(如我们耳濡目染的select、poll、epoll函数),它们可以同时监控多个fd的操作,任何一个返回内核数据就绪,应用进程再发起recvfrom系统调用。

IO多路复用之select:应用进程通过调用select函数,可以同时监控多个fd,在select函数监控的fd中,只要有任何一个数据状态准备就绪了,select函数就会返回可读状态,这时应用进程再发起recvfrom请求去读取数据。

非阻塞IO模型(NIO)中,需要N(N > = 1 N >= 1N>=1)次轮询系统调用,然而借助select的IO多路复用模型,只需要发起一次询问就够了,大大优化了性能。

但是呢,select有几个缺点:

  • 监听的IO最大连接数有限,在Linux系统上一般为1024。

  • select函数返回后,是通过遍历fdset,找到就绪的描述符fd。(仅知道有I/O事件发生,却不知是哪几个流,所以遍历所有流)

因为存在连接数限制,所以后来又提出了poll。与select相比,poll解决了连接数限制问题。但是呢,select和poll一样,还是需要通过遍历文件描述符来获取已经就绪的socket。如果同时连接的大量客户端,在一时刻可能只有极少处于就绪状态,伴随着监视的描述符数量的增长,效率也会线性下降。因此经典的多路复用模型epoll诞生。

IO多路复用之epoll:为了解决select/poll存在的问题,多路复用模型epoll诞生,它采用事件驱动来实现,流程图如下:

epoll先通过epoll_ctl()来注册一个fd(文件描述符),一旦基于某个fd就绪时,内核会采用回调机制,迅速激活这个fd,当进程调用epoll_wait()时便得到通知。这里去掉了遍历文件描述符的坑爹操作,而是采用监听事件回调的机制。这就是epoll的亮点。

select

poll

epoll

底层数据结构

数组

链表

红黑树和双链表

内核实现及工作效率

采用轮询方式检测就绪事件,时间复杂度O(n)

采用轮询方式检测就绪事件,时间复杂度O(n)

采用回调方式检测就绪事件,时间复杂度O(1)

最大连接数

1024

无限制

无限制

工作模式

LT

LT

LT和ET(高效)

fd数据拷贝

每次调用select,需要将fd数据从用户空间拷贝至内核空间

每次调用poll,需要将fd数据从用户空间拷贝至内核空间

使用内存映射(mmap),不需要从用户空间频繁拷贝fd数据至内和空间,降低拷贝的资源消耗

事件集合

通过3个参数分别传入感兴趣的可读、可写、异常等事件。内核通过对这些参数的在线修改来反馈其中的就绪事件,这使得用户每次调用select都要重置这3个参数

统一处理所有事件类型,因此只需要一个事件集参数。用户通过pollfd.events传入感兴趣的事件,内核通过修改pollfd.revents参数反馈其中就绪的事件

内核通过一个事件表直接管理用户感兴趣的所有事件。因此每次调用epoll_wait时,无需反复传入用户感兴趣的事件。epoll_wait系统调用的参数events仅用来反馈就绪的事件

epoll明显优化了IO的执行效率,但在进程调用epoll_wait()时,仍然可能被阻塞。能不能酱紫:不用我老是去问你数据是否准备就绪,等我发出请求后,你数据准备好了通知我就行了,这就诞生了信号驱动IO模型。

相关视频推荐

C/C++网络编程,最新筛选精读视频,网络编程进阶必看(tcp、udp、epoll、网络协议栈、websocket、dpdk、网络穿透、reactor...)icon-default.png?t=N7T8https://www.bilibili.com/video/BV1WG4y1g7H9/

需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

IO模型之信号驱动模型

信号驱动IO不再用主动询问的方式去确认数据是否就绪,而是向内核发送一个信号(调用sigaction的时候建立一个SIGIO的信号),然后应用用户进程可以去做别的事,不用阻塞。当内核数据准备好后,再通过SIGIO信号通知应用进程,数据准备好后的可读状态。应用用户进程收到信号之后,立即调用recvfrom,去读取数据。

信号驱动IO模型,在应用进程发出信号后,是立即返回的,不会阻塞进程。它已经有异步操作的感觉了。但是你细看上面的流程图,发现数据复制到应用缓冲的时候,应用进程还是阻塞的。回过头来看下,不管是BIO,还是NIO,还是信号驱动,在数据从内核复制到应用缓冲的时候,都是阻塞的。还有没有优化方案呢?AIO(真正的异步IO)!

IO 模型之异步IO(AIO)

前面讲的BIO,NIO和信号驱动,在数据从内核复制到应用缓冲的时候,都是阻塞的,因此都不算是真正的异步。AIO实现了IO全流程的非阻塞,就是应用进程发出系统调用后,是立即返回的,但是立即返回的不是处理结果,而是表示提交成功类似的意思。等内核数据准备好,将数据拷贝到用户进程缓冲区,发送信号通知用户进程IO操作执行完毕。

异步IO的优化思路很简单,只需要向内核发送一次请求,就可以完成数据状态询问和数据拷贝的所有操作,并且不用阻塞等待结果。日常开发中,有类似思想的业务场景:比如发起一笔批量转账,但是批量转账处理比较耗时,这时候后端可以先告知前端转账提交成功,等到结果处理完,再通知前端结果即可。

  • 同步阻塞(blocking-IO)简称BIO

  • 同步非阻塞(non-blocking-IO)简称NIO

  • 异步非阻塞(asynchronous-non-blocking-IO)简称AIO

一个经典生活的例子:

  • 小明去吃同仁四季的椰子鸡,就这样在那里排队,等了一小时,然后才开始吃火锅。(BIO)

  • 小红也去同仁四季的椰子鸡,她一看要等挺久的,于是去逛会商场,每次逛一下,就跑回来看看,是不是轮到她了。于是最后她既购了物,又吃上椰子鸡了。(NIO)

  • 小华一样,去吃椰子鸡,由于他是高级会员,所以店长说,你去商场随便逛会吧,等下有位置,我立马打电话给你。于是小华不用干巴巴坐着等,也不用每过一会儿就跑回来看有没有等到,最后也吃上了美味的椰子鸡(AIO)

高性能网络框架

要理解网络框架有哪些,必须要清楚网络框架完成了哪些事情。

大致描述下这个请求处理的流程:

  • 远端的机器A发送了一个HTTP请求到服务器B,此时服务器B网卡接收到数据并产生一个IO可读事件;

  • 我们以同步IO为例,此时内核将该可读事件通知到应用程序的listen线程;

  • listen线程将任务甩给handler线程,由handler将数据从内核读缓冲区拷贝到用户空间读缓冲区;

  • 请求数据包在应用程序内部进行计算和处理并封装响应包;

  • handler线程等待可写事件的到来;

  • 当这个连接可写时将数据从用户态写缓冲区拷贝到内核缓冲区,并通过网卡发送出去;

备注:上述例子是以同步IO为例,并且将线程中的角色分为Listen线程、Handler线程、Worker线程,分别完成不同的工作,后续会详细展开。

所以我们可以知道,要完成一个数据交互,涉及了几大块内容:

  • IO事件监听

  • 数据拷贝

  • 数据处理和计算

我们在网络通信中目前划分两种体系结构,分别为:thread-based architecture(基于线程的架构)、event-driven architecture(事件驱动模型),事件驱动体系结构是目前比较广泛使用的一种。这种方式会定义一系列的事件处理器来响应事件的发生,并且将服务端接受连接与对事件的处理分离。其中,事件是一种状态的改变。比如,tcp中socket的new incoming connection、ready for read、ready for write。Reactor模式和Proactor模式都是事件驱动模型的实现方式。

thread-based architecture 基于线程的架构

在早期并发数不多的场景中,有一种One Request One Thread的架构模式。通俗的说就是:多线程并发模式,一个连接一个线程,服务器每当收到客户端的一个请求, 便开启一个独立的线程来处理。该模式下每次接收一个新请求就创建一个处理线程,线程虽然消耗资源并不多,但是成千上万请求打过来,性能也是扛不住的。

这是一种比较原始的架构,思路也非常清晰,创建多个线程来提供处理能力,这种模式一定程度上极大地提高了服务器的吞吐量,由于在不同线程中,之前的请求在read阻塞以后,不会影响到后续的请求。但是,仅适用于于并发量不大的场景,因为:

  • 线程需要占用一定的内存资源

  • 创建和销毁线程也需一定的代价

  • 操作系统在切换线程也需要一定的开销

  • 线程处理I/O,在等待输入或输出的这段时间处于空闲的状态,同样也会造成CPU资源的浪费

如果连接数太高,系统将无法承受,不再展开讨论此模型。

event-driven architecture 事件驱动模型

当前流行的是基于事件驱动的IO复用模型,相比多线程模型优势很明显。

在此我们先理解一下什么是Event-Drive-Model事件驱动模型:事件驱动编程是一种编程范式,程序的执行流由外部事件来决定,它的特点是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理。

通俗来说就是:有一个循环装置在一直等待各种事件的到来,并将到达的事件放到队列中,再由一个分拣装置来调用对应的处理装置来响应。

Reactor反应堆模式

反应堆模式是一种思想,形式却有很多种。反应堆的本质是什么呢?从本质上理解,无论什么网络框架都要完成两部分操作:

  • IO操作:数据包的读取和写入

  • CPU操作:数据请求的处理和封装

所以上述这些问题由谁来做以及多少线程来做,就衍生出了很多形式,所以不要被表面现象迷惑,出现必有原因,追溯之后我们才能真正掌握它。

反应堆模式根据处理IO环节和处理数据环节的数量差异分为如下几种:

  • 单Reactor线程

  • 单Reactor线程和线程池

  • 多Reactor线程和线程池

我们来看看这三种常见模式的特点、原理、优缺点、应用场景等。

单Reactor线程模式

这种模式最为简洁,主要是针对于I/O操作而言,一个线程完成了连接的监听listen()、接收新连接accept()、处理连接connect()、读取数据read()、写入数据write()全套工作。由于只使用了一个线程,对于多核利用率偏低,但是编程简单。是不是觉得这个种单线程的模式没有市场?那可未必,不信你看Redis。

但在目前的单线程Reactor模式中,在这种模式种IO操作和CPU操作是没有分开的,不仅I/O操作在该Reactor线程上,连非I/O的业务操作也在该线程上进行处理了,显然如果在Handler处理某个请求超时了将会阻塞客户端的正常连接,这可能会大大延迟I/O请求的响应。在Redis中由于都是内存操作,速度很快,这种瓶颈虽然存在但是不够明显。我们应该将非I/O的业务逻辑操作从Reactor线程上卸载,以此来加速Reactor线程对I/O请求的响应。

单Reactor线程和线程池模式

单Reactor线程模式的IO操作和CPU操作是在一个线程内部串行执行的,这样就拉低了CPU操作效率。为了解决IO操作和CPU操作的不匹配,将IO操作和CPU操作分别由单独的线程来完成,相互不影响。单Reactor线程完成IO操作、复用工作线程池来完成CPU操作,添加了一个工作者线程池,并将非I/O操作从Reactor线程中移出转交给工作者线程池(Thread Pool)来执行就是一种解决思路。这样能够提高Reactor线程的I/O响应,不至于因为一些耗时的业务逻辑而延迟对后面I/O请求的处理。

在工作者线程池模式中,虽然非I/O操作交给了线程池来处理,但是所有的I/O操作依然由Reactor单线程执行,在高负载、高并发或大数据量的应用场景,依然较容易成为瓶颈。

所以,对于Reactor的优化,又产生出下面的多Reactor线程和线程池模式。

多Reactor线程和线程池模式

水平扩展往往是提供性能的有效方法。

我们将Reactor线程进行扩展,将Reactor划分为两部分:mainReactor和subReactor,一个mainReactor线程负责处理新连接,其余多个subReactor线程负责处理连接成功的IO数据读写。也就是进一步将监听、创建连接和处理连接,分别由两个及以上的线程来完成,进一步提高了IO操作部分的效率。

mainReactor负责监听server socket,用来处理网络新连接的建立,将建立的socketChannel指定注册给subReactor,通常一个线程就可以处理;subReactor维护自己的selector,基于mainReactor注册的socketChannel多路分离I/O读写事件,读写网络数据,通常使用多线程;对非I/O的操作,依然转交给工作者线程池(Thread Pool)执行。此种模型中,每个模块的工作更加专一,耦合度更低,性能和稳定性也大量的提升,支持的可并发客户端数量可达到上百万级别。在实际生产环境算是比较高配的版本了。

以上就是对Reactor反应堆模式的总结,Reactor模式是一种被动的处理模式,当有事件发生时被动处理事件。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1350970.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023年终总结---做一个有生活的人

2023总结 【工作】2023一整年都在阿里淘天集团工作。作为工作的第二年,学习到很多知识和设计方法,从而某个中心的左膀右臂。敢在需求评审中说不,说话变的有底气。中间经历了组织架构的一次一次调整,当时面试自己的一个师兄因家庭原…

CSS-4

平面转换 整体认识 div {margin: 100px 0;width: 100px;height: 100px;background-color: pink;/* 过渡效果 */transition: all 1s;}/* 当鼠标悬停到div时,进行平面转换 */div:hover {transform: translate(800px) rotate(360deg) scale(2) skew(180deg);}作用&…

Python爬虫——使用代理IP池维护虚拟用户

目录 前言 一、什么是代理IP池? 二、爬取代理IP 三、验证代理IP的可用性 四、维护代理IP池 五、使用代理IP池进行爬取 六、总结 前言 在进行Web爬取时,使用代理IP是一种常见的策略,它可以帮助我们隐藏真实IP地址,绕过网站…

PyQT 多进程

在PyQt中,图形化界面(GUI)是运行在主线程中的,而多进程是在独立的进程中执行的。默认情况下,多进程之间是无法直接共享图形化界面的。 然而,有几种方法可以在多进程中与PyQt的图形化界面进行通信&#xff…

Wnmp本地部署结合内网穿透实现任意浏览器远程访问本地服务

最近,我发现了一个超级强大的人工智能学习网站。它以通俗易懂的方式呈现复杂的概念,而且内容风趣幽默。我觉得它对大家可能会有所帮助,所以我在此分享。点击这里跳转到网站。 文章目录 前言1.Wnmp下载安装2.Wnmp设置3.安装cpolar内网穿透3.1…

Linux驱动开发学习笔记7《并发与竞争》

目录 一、并发与竞争 1、并发与竞争简介 2、保护内容是什么 二、原子操作 1、 原子操作简介 2、原子整形操作API 函数 3、原子位操作API 函数 4、实验 (1) 修改设备树文件 (2) LED 驱动修改 (3)…

2023年度总结———豫见及遇见

目录 一.AI 人工智能方向 二.华为数通网络方向 三.腾讯云AI绘画方向 四.年度心得总结板块 博主C站主页:知孤云出岫_网络,计算机,计算机网络教案-CSDN博客 博主腾讯云主页: 知孤云出岫 - 个人中心 - 腾讯云开发者社区-腾讯云 2023年年度词&#xff1a…

集合高级知识点

集合高级 1. HashSet 底层原理 HashSet 的特点: HashSet 实现了 Set 接口HashSet 底层实质上是 HashMap可以存放 null 值,但是只能有一个 nullHashSet 不保证元素是有序的,取决于 hash 后,再确定索引的结果,即不保证…

Python 从入门到精通之通俗易懂学闭包

系列 Python从入门到精通之安装与快速入门-CSDN博客 Python从入门到精通之基本数据类型和变量-CSDN博客 Python从入门到精通之集合(List列表、Tuple元组、Dict字典、Set)-CSDN博客 Python从入门到精通之条件语句、循环语句和函数-CSDN博客 Python从…

单片机原理及应用:计数按键控制数码管显示

承接上文&#xff0c;我们来介绍一下按键和数码管的配合工作&#xff0c;由于数码管显示的字符和位数多种多样&#xff0c;无法做到一个字符对应一个按键&#xff0c;所以程序主要记录按键的使用次数来切换数码管的显示。 #include <reg52.h> //包含reg52.h头…

如何使用SeaFile搭建本地私有云盘并结合cpolar实现远程访问

文章目录 1. 前言2. SeaFile云盘设置2.1 SeaFile的安装环境设置2.2 SeaFile下载安装2.3 SeaFile的配置 3. cpolar内网穿透3.1 Cpolar下载安装3.2 Cpolar的注册3.3 Cpolar云端设置3.4 Cpolar本地设置 4.公网访问测试5.结语 1. 前言 现在我们身边的只能设备越来越多&#xff0c;…

2024洗地机哪家强?口碑洗地机推荐

现如今&#xff0c;智能家电在人们生活中变得越来越受欢迎&#xff0c;例如智能洗地机的出现&#xff0c;不仅省时省力&#xff0c;还实现了家务清洁的自由。在家庭中&#xff0c;地面清洁一直是一个令人头疼的问题&#xff0c;各种智能家居品牌通过开发各种智能家电产品来解决…

【工具】vscode搜索结果及工程目录的文件夹、文件的排除

&#x1f41a;作者简介&#xff1a;花神庙码农&#xff08;专注于Linux、WLAN、TCP/IP、Python等技术方向&#xff09;&#x1f433;博客主页&#xff1a;花神庙码农 &#xff0c;地址&#xff1a;https://blog.csdn.net/qxhgd&#x1f310;系列专栏&#xff1a;善假于物&#…

【模拟电路】NE555-电子琴应用原理

一、声音和频率 二、振荡周期和占空比 三、NE555电子琴案例 四、NE555内部和方波发生器 一、声音和频率 声音和频率的关系确实是密切相关的。 在声学中&#xff0c;声音的频率被定义为声波的震动次数&#xff0c;通常以赫兹&#xff08;Hz&#xff09;为单位。频率越高&#x…

log4cplus visual c++ 编译及调试小记

简介 最近在调试一款SATA加密设备&#xff0c;发现设备有时加密出来的数据&#xff0c;再解密时与明文对不上&#xff0c;怀疑是通信问题。因此&#xff0c;急需要在测试工具中加入通信日志。由于对第三方日志库都不熟悉&#xff0c;所以随便选了个log4cplus软件集成到现有工具…

leetcode2975. 移除栅栏得到的正方形田地的最大面积

题目 有一个大型的 (m - 1) x (n - 1) 矩形田地&#xff0c;其两个对角分别是 (1, 1) 和 (m, n) &#xff0c;田地内部有一些水平栅栏和垂直栅栏&#xff0c;分别由数组 hFences 和 vFences 给出。 水平栅栏为坐标 (hFences[i], 1) 到 (hFences[i], n)&#xff0c;垂直栅栏为…

STM32L4

STM32L4系列超低功耗微控制器 意法半导体通过构建新型芯片架构实现了同类产品中最佳的超低功耗及性能&#xff0c;这得益于应用设计上的高度灵活性。 STM32L4系列可以根据微处理器运行时不同的应用需求来适时调整电压从而实现功耗的动态平衡。 该系列包含不同的产品线&#…

(15)Linux 进程创建与终止函数forkslab 分派器

前言&#xff1a;本章我们主要讲解进程的创建与终止&#xff0c;最后简单介绍一下 slab 分派器。 一、进程创建&#xff08;Process creation&#xff09; 1、分叉函数 fork 在 中&#xff0c; fork 函数是非常重要的函数&#xff0c;它从已存在进程中创建一个新的进程。 …

OCP NVME SSD规范解读-3.NVMe管理命令-part2

NVMe-AD-8&#xff1a;在某些情况下&#xff08;如Sanitize命令、Format NVM命令或TCG Revert方法后数据被清除&#xff09;&#xff0c;设备应允许读取已清除的LBAs而不产生错误&#xff0c;并在最后一次清除完成后&#xff0c;对未写入LBAs的读取返回所有零值给主机 NVMe-AD…

闭包,垃圾回收机制

1.垃圾回收机制 当函数执行完毕后,函数内部的变量就会被销毁。 代码&#xff1a; function fn() {var a 10;a;return a;}console.log(fn()); 输出的结果: 11 持续调用的结果: 2.变量的私有化 代码: function fn() {var a 10;return function fn1() {return a;}…