一文搞懂网络通信的基石✅IO模型与零拷贝

简介: 【10月更文挑战第1天】本文深入探讨了网络通信中的IO模型及其优化方法——零拷贝技术。首先介绍了IO模型的概念及五种常见类型:同步阻塞、同步非阻塞、多路复用、信号驱动和异步IO模型。文章详细分析了每种模型的特点和适用场景,特别是多路复用和异步IO在高并发场景中的优势。接着介绍了零拷贝技术,通过DMA直接进行数据传输,避免了多次CPU拷贝,进一步提升了效率。最后总结了各种模型的优缺点,并提供了相关的代码示例和资源链接。

网络通信的基石:IO模型与零拷贝

中间件作为现代软件架构的基石,扮演着承上启下的关键角色,它不仅衔接了多样化的服务与系统,还极大地促进数据的流动与处理

而这一切高效运作的背后,网络通信是各大中间件中不可或缺的一环

如常见的WEB服务器(tomcat、jetty、undertow),数据库(MySQL、Redis),MQ...

它们都需要进行网络通信,那么如何才能高效的进行网络通信呢?

在聊这个话题前,我们需要先聊聊IO模型

什么是IO模型呢?

IO即输入/输出,IO模型的提出主要是解决计算机CPU在内存与磁盘/网卡等外部设备速度不匹配的问题

当CPU想要读取磁盘/网卡上的数据时,数据拷贝到内存是需要时间的,那这时CPU是去等待数据拷贝完成,还是先去执行其他任务呢?

举个简单的例子:

精通CRUD的小菜快速完成简单的CRUD,但还需要等待其他部门提供的接口,由于其他部门的业务比较复杂,接口文档可能要几天后才能给出,小菜想趁着这段时间休息一会

可是作为小菜上司的我可不乐意了,还有这么多开发任务呢,我再分一个任务给小菜做,让小菜不能空闲下来,等到后续其他部门的接口写好了,再通知小菜完成这个开发任务(提升小菜的“吞吐量”)

这个案例中小菜就是CPU,完成简单的CRUD可以看成在内存上操作(速度快),等待其他部门的接口可以看成等待外部设备把数据拷贝到内存(速度慢)

为了解决这个问题,我提出一种“IO模型”:让小菜先去干别的活,等其他部门的接口好了再通知小菜回来完成这个开发任务

步入正题,常见的IO模型分为五种:同步阻塞IO模型、同步非阻塞IO模型、多路复用IO模型、信号驱动IO模型、异步IO模型

处理流程

为了更好的理解IO模型,先举个体现总体流程的下载文件案例:

下载文件案例中,客户端先向服务端发送请求,而服务器收到请求后,读取磁盘中的文件数据,发送到网卡上再响应给客户端

在这个过程中,以服务端的视角可以看成先从磁盘中读取数据,再往网卡上写数据

由于磁盘、网卡属于外部设备,由于外部设备速度慢,不会使用CPU进行拷贝数据,而是通过DMA进行数据拷贝(这样就不需要占用CPU的资源)

操作系统分为用户态和内核态,其中应用程序处于用户态

由于操作系统中的重要资源不能被用户态的应用程序直接访问,需要先切换到内核态再进行访问

image.png

用传统的阻塞IO(BIO)举例,当服务端接收到请求后:

  1. 读取磁盘要先切换为内核态,然后由DMA进行拷贝(将磁盘上的数据拷贝到内核缓冲区),此时用户线程一直阻塞
  2. DMA拷贝完成后,由CPU将数据从内核缓冲区拷贝到用户态内存上的用户缓冲区,此时用户线程才被唤醒
  3. 状态切换为用户态后,用户线程从用户态中内存的缓冲区将数据拷贝到JVM的堆内存缓冲区中
  4. Java程序处理数据(已经读完),开始写数据,写数据的流程与读数据流程类似,只不过是相反的

图中的直接内存为用户态的内存,而堆内存为JVM的(属于JVM管理)

在这个过程中:用户线程一直阻塞,读数据与写数据存在大量重复拷贝、状态切换,都会导致性能被大大浪费

通过IO模型、零拷贝等优化方式能够优化这个过程,提升响应速度

IO模型

同步阻塞

在同步阻塞IO模型下,当用户线程请求读取外部设备的数据时,会一直阻塞直到数据拷贝完成,再去使用数据

image.png

具体流程如下:

  1. 用户线程发起系统调用请求读取外部设备数据(进入阻塞)
  2. 用户态切换为内核态准备数据:使用DMA将外部设备数据拷贝到内核缓冲区
  3. 内核进行数据拷贝:将内核缓冲区的数据拷贝到用户缓冲区
  4. 内核态切换为用户态,使用数据

阻塞指的是:在用户线程发起系统调用时,并没有立即返回,而是等到数据拷贝完成被唤醒再使用

同步指的是:在数据拷贝阶段,用户线程是阻塞等待数据完成拷贝的(也可以理解为同步是用户线程主动请求内核进行数据拷贝的)

在同步阻塞IO模型下,由于准备、拷贝数据阶段都会阻塞等待,因此性能并不理想

如果高并发的请求打进来,让等比例的线程数量来等待,资源开销是非常大的,因此现代中间件一般不会采取这种模型

同步非阻塞

同步非阻塞IO模型会频繁发起系统调用来判断数据是否已就绪,如果已就绪则同步阻塞进行拷贝

在这个过程中,准备数据阶段是通过轮询非阻塞的方式实现的,当响应数据就绪时,再发起系统调用同步阻塞进行数据拷贝

image.png

同步非阻塞IO模型虽然在数据准备的阶段不需要阻塞,但会通过轮询的方式一直进行系统调用,产生一定的开销

要求网络通信高效的中间件也不会使用这种模型

多路复用

在多路复用模型中,内核线程能够同时监听多个网络请求的通道

使用前,会将数据通道注册到select上,当使用select时会进行阻塞,直到select监听到数据通道上数据已就绪,此时再请求读取数据,使用read系统调用,同步阻塞直到拷贝完数据

image.png

在多路复用模型中实现还分为三种方式:select、poll、epoll

select就是上述举例流程,缺点是最多监听1024个数据通道,并且阻塞到数据就绪时需要遍历处理O(n)

poll在select基础上,动态调整只要内存够理论上无监听通道数量的上限,但数据就绪时还需要遍历处理

epoll使用事件回调的方式,当数据就绪时不需要再轮询,并且内核维护不再需要将数据拷贝到用户态

在多路复用模型中,由于一个内核线程可以监听多个数据通道,这样即使维护大量的网络数据通道,开销也不会太大

而且有epoll事件回调、不用拷贝的优化性能非常好,大部分的中间件都会选择多路复用模型实现网络通信

信号驱动

在信号驱动模型中,会先发送信号的系统调用(立即返回 非阻塞),当数据准备好后通知,再发送读数据的系统调用(阻塞),让内核完成拷贝数据

image.png

信号驱动避免准备数据时的阻塞,并且不需要轮询发起系统调用,但在数据拷贝时依旧需要同步阻塞

异步

在异步IO模型中,发起请求的系统调用时会携带回调函数,发起系统调用后立即返回(非阻塞)

当数据就绪后,不需要用户线程同步触发,而是由内核主动将数据拷贝到用户缓冲区

image.png

在异步IO(AIO)中完全没有阻塞也不再需要同步

在要求高效的高并发网络通信中,一般使用多路复用模型NIO和异步IO(AIO)

JDK中的NIO指的就是多路复用模型,而NIO2指的就是AIO,后续讲解中间件如何高效处理网络通信时都会出现它们的身影~

零拷贝

聊完IO模型后,我们能够知道使用NIO、AIO能够加快处理流程的速度

处理流程中还存在大量的CPU拷贝,在Linux内核逐步升级后,网卡支持的情况下还可以实现零拷贝

零拷贝指的是不再需要使用CPU进行数据拷贝,而是直接通过DMA进行拷贝

image.png

为什么无法从内核直接拷贝到JVM堆内存?

在传统的流程处理中,需要先从内核拷贝到直接内存,再从直接内存拷贝到JVM堆内存

既然直接内存和JVM堆内存都处于用户态的内存,为什么不从内核直接拷贝到JVM堆内存呢?

这是由于JVM堆内存会发生GC,可能改变位置,内核拷贝到堆内存时无法保证不会GC

而本地内存拷贝到JVM堆内存,HotSpot虚拟机保证不在安全点上,因此不会GC

JVM安全点相关知识感兴趣的同学可以查看这篇文章

总结

IO模型的提出是为了解决CPU在内存的速度与外部设备加载到内存速度的差异

在操作系统中为了安全使用系统资源,IO时会涉及到用户态、内核态的切换

IO阶段通常分为准备数据和拷贝数据,准备数据主要由DMA将外部设备数据拷贝到内核缓冲区,拷贝数据是将内核缓冲区拷贝到用户缓冲区

同步阻塞IO模型(BIO)发起系统调用后会阻塞到数据拷贝完成,不适合处理高并发网络通信的场景

同步非阻塞IO模型使用轮询的方式判断数据是否就绪,就绪再同步阻塞等待数据拷贝

信号驱动模型中数据就绪后通过信号通知应用发起系统调用读取数据,避免同步非阻塞下轮询的开销

多路复用IO模型使用select时,监听多个通道,select阻塞直到监听到通道上数据就绪,再通知应用进行读取发起同步阻塞直到数据拷贝结束

使用select,当多个通道数据同时就绪时,只能轮询处理,并且只能监听1024个通道;使用poll进行优化能够监听无上限通道数量

使用epoll 事件回调的方式避免轮询处理,并且内核维护不需要再进行数据拷贝

异步IO模型使用回调的方式避免数据就绪时同步阻塞进行数据拷贝,Linux下也是使用epoll模拟实现

当网卡支持时使用sendfile零拷贝可以避免大量CPU数据拷贝

最后(不要白嫖,一键三连求求拉~)

本篇文章被收入专栏 后端的网络基石,感兴趣的同学可以持续关注喔

本篇文章笔记以及案例被收入 Gitee-CaiCaiJavaGithub-CaiCaiJava,除此之外还有更多Java进阶相关知识,感兴趣的同学可以starred持续关注喔~

有什么问题可以在评论区交流,如果觉得菜菜写的不错,可以点赞、关注、收藏支持一下~

关注菜菜,分享更多技术干货,公众号:菜菜的后端私房菜

相关文章
|
23天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
15天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
20天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2572 22
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
18天前
|
人工智能 IDE 程序员
期盼已久!通义灵码 AI 程序员开启邀测,全流程开发仅用几分钟
在云栖大会上,阿里云云原生应用平台负责人丁宇宣布,「通义灵码」完成全面升级,并正式发布 AI 程序员。
|
3天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】
|
2天前
|
存储 人工智能 搜索推荐
数据治理,是时候打破刻板印象了
瓴羊智能数据建设与治理产品Datapin全面升级,可演进扩展的数据架构体系为企业数据治理预留发展空间,推出敏捷版用以解决企业数据量不大但需构建数据的场景问题,基于大模型打造的DataAgent更是为企业用好数据资产提供了便利。
159 2
|
19天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1570 16
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
|
21天前
|
编解码 JSON 自然语言处理
通义千问重磅开源Qwen2.5,性能超越Llama
击败Meta,阿里Qwen2.5再登全球开源大模型王座
943 14
|
3天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
187 2
|
16天前
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
711 10