【RPC】网络通信:哪种网络IO模型最适合RPC框架?

一、背景

RPC是解决进程间通信的一种方式。一次RPC调用,本质就是服务消费者与服务提供者间的一次网络信息交换的过程。服务调用者通过网络IO发送一条请求消息,服务提供者接收并解析,处理完相关的业务逻辑之后,再发送一条响应消息给服务调用者,服务调用者接收并解析响应消息,处理完相关的响应逻辑,一次RPC调用便结束了。可以说,网络通信是整个RPC调用流程的基础。

二、常见的网络IO模型

那说到网络通信,就不得不提一下网络IO模型。为什么要讲网络IO模型呢?因为所谓的两台PC机之间的网络通信,实际上就是两台PC机对网络IO的操作。

常见的网络IO模型分为四种:同步阻塞IO(BIO)、同步非阻塞IO(NIO)、IO多路复用和异步非阻塞IO(AIO)。在这四种IO模型中,只有AIO为异步IO,其他都是同步IO。

其中,最常用的就是同步阻塞IO和IO多路复用。

2.1、阻塞IO(blocking IO)

同步阻塞IO是最简单、最常见的IO模型,在Linux中,默认情况下所有的socket都是blocking的,先看下操作流程。

首先,应用进程发起IO系统调用后,应用进程被阻塞,转到内核空间处理。之后,内核开始等待数据,等待到数据之后,再将内核中的数据拷贝到用户内存中,整个IO处理完毕后返回进程。最后应用的进程解除阻塞状态,运行业务逻辑。

这里我们可以看到,系统内核处理IO操作分为两个阶段——等待数据和拷贝数据。而在这两个阶段中,应用进程中IO操作的线程会一直都处于阻塞状态,如果是基于Java多线程开发,那么每一个IO操作都要占用线程,直至IO操作结束。

这个流程就好比我们去餐厅吃饭,我们到达餐厅,向服务员点餐,之后要一直在餐厅等待后厨将菜做好,然后服务员会将菜端给我们,我们才能享用。

2.2、IO多路复用(IO multiplexing)

多路复用IO是在高并发场景中使用最为广泛的一种IO模型,如Java的NIO、Redis、Nginx的底层实现就是此类IO模型的应用,经典的Reactor模式也是基于此类IO模型。

那么什么是IO多路复用呢?通过字面上的理解,多路就是指多个通道,也就是多个网络连接的IO,而复用就是指多个通道复用在一个复用器上。

多个网络连接的IO可以注册到一个复用器(select)上,当用户进程调用了select,那么整个进程会被阻塞。同时,内核会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从内核中拷贝到用户进程。

在多路复用模型中,内核仅有一条线程负责处理所有连接,所有网络请求/连接(Socket)都会利用通道Channel注册到选择器上,然后监听器负责监听所有的连接,过程如下:

阶段一:

  • 用户进程调用select,指定要监听的FD集合

  • 核监听FD对应的多个socket

  • 任意一个或多个socket数据就绪则返回readable

  • 此过程中用户进程阻塞

阶段二:

  • 用户进程找到就绪的socket

  • 依次调用recvfrom读取数据

  • 内核将数据拷贝到用户空间

  • 用户进程处理数据

当用户去读取数据的时候,不再去直接调用recvfrom了,而是调用select的函数,select函数会将需要监听的数据交给内核,由内核去检查这些数据是否就绪了,如果说这个数据就绪了,就会通知应用程序数据就绪,然后来读取数据,再从内核中把数据拷贝给用户态,完成数据处理,如果N多个FD一个都没处理完,此时就进行等待。

同样好比我们去餐厅吃饭,这次我们是几个人一起去的,我们专门留了一个人在餐厅排号等位,其他人就去逛街了,等排号的朋友通知我们可以吃饭了,我们就直接去享用了。

2.3、为什么说阻塞lO和lO多路复用最为常用?

了解完二者的机制,我们就可以回到起初的问题了——我为什么说阻塞IO和IO多路复用最为常用。对比这四种网络IO模型:阻塞IO、非阻塞IO、IO多路复用、异步IO。实际在网络IO的应用上,需要的是系统内核的支持以及编程语言的支持。

在系统内核的支持上,现在大多数系统内核都会支持阻塞IO、非阻塞IO和IO多路复用,但像信号驱动IO、异步IO,只有高版本的Linux系统内核才会支持。

在编程语言上,无论C++还是Java,在高性能的网络编程框架的编写上,大多数都是基于Reactor模式,其中最为典型的便是Java的Netty框架,而Reactor模式是基于IO多路复用的。当然,在非高并发场景下,同步阻塞IO是最为常见的。

综合来讲,在这四种常用的IO模型中,应用最多的、系统内核与编程语言支持最为完善的,便是阻塞IO和IO多路复用。这两种IO模型,已经可以满足绝大多数网络IO的应用场景。

2.4、RPC框架在网络通信上倾向选择哪种网络IO模型?

IO多路复用更适合高并发的场景,可以用较少的进程(线程)处理较多的socket的IO请求,但使用难度比较高。当然高级的编程语言支持得还是比较好的,比如Java语言有很多的开源框架对Java原生API做了封装,如Netty框架,使用非常简便;而GO语言,语言本身对IO多路复用的封装就已经很简洁了。

而阻塞IO与IO多路复用相比,阻塞IO每处理一个socket的IO请求都会阻塞进程(线程),但使用难度较低。在并发量较低、业务逻辑只需要同步进行IO操作的场景下,阻塞IO已经满足了需求,并且不需要发起select调用,开销上还要比IO多路复用低。

RPC调用在大多数的情况下,是一个高并发调用的场景,考虑到系统内核的支持、编程语言的支持以及IO模型本身的特点,在RPC框架的实现中,在网络通信的处理上,我们会选择IO多路复用的方式。开发语言的网络通信框架的选型上,我们最优的选择是基于Reactor模式实现的框架,如Java语言,首选的框架便是Netty框架(Java还有很多其他NIO框架,但目前Netty应用得最为广泛),并且在Linux环境下,也要开启epoll来提升系统性能(Windows环境下是无法开启epoll的,因为系统内核不支持)。

了解完以上内容,我们可以继续看这样一个关键问题——零拷贝。在我们应用的过程中,他是非常重要的。

三、什么是零拷贝?

系统内核处理IO操作分为两个阶段——等待数据和拷贝数据。等待数据,就是系统内核在等待网卡接收到数据后,把数据写到内核中;而拷贝数据,就是系统内核在获取到数据后,将数据拷贝到用户进程的空间中。以下是具体流程:

应用进程的每一次写操作,都会把数据写到用户空间的缓冲区中,再由CPU将数据拷贝到系统内核的缓冲区中,之后再由DMA将这份数据拷贝到网卡中,最后由网卡发送出去。这里我们可以看到,一次写操作数据要拷贝两次才能通过网卡发送出去,而用户进程的读操作则是将整个流程反过来,数据同样会拷贝两次才能让应用程序读取到数据。

应用进程的一次完整的读写操作,都需要在用户空间与内核空间中来回拷贝,并且每一次拷贝,都需要CPU进行一次上下文切换(由用户进程切换到系统内核,或由系统内核切换到用户进程),这样是不是很浪费CPU和性能呢?那有没有什么方式,可以减少进程间的数据拷贝,提高数据传输的效率呢?

这时我们就需要零拷贝(Zero-copy)技术。

所谓的零拷贝,就是取消用户空间与内核空间之间的数据拷贝操作,应用进程每一次的读写操作,都可以通过一种方式,让应用进程向用户空间写入或者读取数据,就如同直接向内核空间写入或者读取数据一样,再通过DMA将内核中的数据拷贝到网卡,或将网卡中的数据copy到内核。

那怎么做到零拷贝?你想一下是不是用户空间与内核空间都将数据写到一个地方,就不需要拷贝了?此时你有没有想到虚拟内存?

零拷贝有两种解决方式,分别是 mmap+write 方式和 sendfile 方式,mmap+write方式的核心原理就是通过虚拟内存来解决的。

参考:【操作系统】一文带你深入浅出零拷贝技术

四、Netty中的零拷贝

我刚才讲到,RPC框架在网络通信框架的选型上,我们最优的选择是基于Reactor模式实现的框架,如Java语言,首选的便是Netty框架。那么Netty框架是否也有零拷贝机制呢?Netty框架中的零拷贝和我之前讲的零拷贝又有什么不同呢?

刚才我讲的零拷贝是操作系统层面上的零拷贝,主要目标是避免用户空间与内核空间之间的数据拷贝操作,可以提升CPU的利用率。

而Netty的零拷贝则不大一样,他完全站在了用户空间上,也就是JVM上,它的零拷贝主要是偏向于数据操作的优化上。

在传输过程中,RPC并不会把请求参数的所有二进制数据整体一下子发送到对端机器上,中间可能会拆分成好几个数据包,也可能会合并其他请求的数据包,所以消息都需要有边界。那么一端的机器收到消息之后,就需要对数据包进行处理,根据边界对数据包进行分割和合并,最终获得一条完整的消息。

那收到消息后,对数据包的分割和合并,是在用户空间完成,还是在内核空间完成的呢?

当然是在用户空间,因为对数据包的处理工作都是由应用程序来处理的,那么这里有没有可能存在数据的拷贝操作?可能会存在,当然不是在用户空间与内核空间之间的拷贝,是用户空间内部内存中的拷贝处理操作。Netty的零拷贝就是为了解决这个问题,在用户空间对数据操作进行优化。

那么Netty是怎么对数据操作进行优化的呢?

  • Netty 提供了 CompositeByteBuf 类,它可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf,避免了各个 ByteBuf 之间的拷贝。

  • ByteBuf 支持 slice 操作,因此可以将 ByteBuf 分解为多个共享同一个存储区域的 ByteBuf,避免了内存的拷贝。

  • 通过 wrap 操作,我们可以将 byte[] 数组、ByteBuf、ByteBuffer 等包装成一个 Netty ByteBuf 对象, 进而避免拷贝操作。

Netty框架中很多内部的ChannelHandler实现类,都是通过CompositeByteBuf、slice、wrap操作来处理TCP传输中的拆包与粘包问题的。

那么Netty有没有解决用户空间与内核空间之间的数据拷贝问题的方法呢?

Netty 的 ByteBuffer 可以采用 Direct Buffers,使用堆外直接内存进行Socket的读写操作,最终的效果与我刚才讲解的虚拟内存所实现的效果是一样的。

Netty 还提供 FileRegion 中包装 NIO 的 FileChannel.transferTo() 方法实现了零拷贝,这与Linux 中的 sendfile 方式在原理上也是一样的。

五、总结

考虑到系统内核的支持、编程语言的支持以及IO模型本身的特点,RPC框架在网络通信的处理上,我们更倾向选择IO多路复用的方式。

零拷贝带来的好处就是避免没必要的CPU拷贝,让CPU解脱出来去做其他的事,同时也减少了CPU在用户空间与内核空间之间的上下文切换,从而提升了网络通信效率与应用程序的整体性能。

而Netty的零拷贝与操作系统的零拷贝是有些区别的,Netty的零拷贝偏向于用户空间中对数据操作的优化,这对处理TCP传输中的拆包粘包问题有着重要的意义,对应用程序处理请求数据与返回数据也有重要的意义。

在 RPC框架的开发与使用过程中,我们要深入了解网络通信相关的原理知识,尽量做到零拷贝,如使用Netty框架;我们要合理使用ByteBuf子类,做到完全零拷贝,提升RPC框架的整体性能。

六、参考

  • 【操作系统】IO模型篇之从BIO、NIO、AIO到内核select、epoll剖析

  • 【操作系统】高性能网络模式:Reactor 和 Proactor

  • 【操作系统】一文带你深入浅出零拷贝技术

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

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

相关文章

重学Java 7 数组Arr.1

我欲与君相知,长命无绝衰 ——24.1.16 一、数组的定义 1.概述:数组是一个容器,数组本身属于引用数据类型 2.作用:一次存储多个数据 3.特点: ①既可以存储基本类型的数据,也可以存储引用类型的数据 ②定长&a…

【软件测试】学习笔记-统一测试数据平台

这篇文章主要探讨全球大型电商企业中关于准备测试数据的最佳实践,从全球大型电商企业早期的测试数据准备实践谈起,分析这些测试数据准备方法在落地时遇到的问题,以及如何在实践中解决这些问题。其实,这种分析问题、解决问题的思路…

mathtype2024版本下载与安装(mac版本也包含在内)

安装包补丁主要是mathtype的安装包,与它的补丁。 详细安装过程: step1: 使用方法是下载完成后先安装MathType-win-zh.exe文件,跟着步骤走直接安装就行。 step2: 关闭之后,以管理员身份运行MathType7PJ.exe…

CF1178F2 Long Colorful Strip 题解 搜索

Long Colorful Strip 传送门 题面翻译 题目描述 这是 F 题的第二个子任务。F1 和 F2 的区别仅在对于 m m m 和时间的限制上 有 n 1 n1 n1 种颜色标号从 0 0 0 到 n n n,我们有一条全部染成颜色 0 0 0 的长为 m m m 的纸带。 Alice 拿着刷子通过以下的过…

一、ArcGIS Pro SDK for Microsoft .NET 开发环境配置

ArcGIS Pro二次开发需要的工具: 1.Visual Studio 2.ArcGIS Pro SDK 一、Visual Studio安装 经过查阅资料,ArcGIS Pro3.0版本需要安装Visual Studio2022版,因为只有22版的才会有有ArcGIS Pro3.0以上版对应ArcGIS Pro SDK,因此&…

如何编译openssl的早期版本的共享库,如openssl 1.0

背景介绍 最近在为客户排查问题的时候,发现客户提供的日志是加密的,解密工具依赖到了openssl 1.0的共享库。可是手头没有这么老版本的openssl共享库。因此只好手动编译一个出来。 编译步骤 因为openssl 1.0是比较老的版本,很多系统上的库已…

新能源汽车智慧充电桩解决方案:智慧化综合管理与数字化高效运营

一、方案概述 TSINGSEE青犀&触角云新能源汽车智慧充电桩解决方案基于管理运营平台,覆盖业务与应用、数据传输与梳理、多端开发、搭建等模块,融合AI、5G、Wi-Fi 、移动支付等技术,实现充电基础设施由数字化向智能化演进,通过构…

翻译: Pyenv管理Python版本从入门到精通一

你是否经常在管理系统上多个Python版本时遇到困难?这可能是一个艰巨的任务,尤其是在处理需要不同Python版本的不同项目时。 但别担心,有一个解决方案:pyenv。就像一个熟练的杂技演员,pyenv可以轻松处理多个Python版本…

连接超时的问题

连接超时的问题 通用第三方工具连接超时 connect timeout 方案一: /etc/ssh/sshd_config node1上操作,图是错的 方案二: windows上Hosts文件域名解析有问题 比如: 192.168.xx.100 node1 192.168.xx.161 node1 两个都解析成node…

绝地求生:【PC】未授权程序使用行为的相关公告

各位玩家大家好, 最近闲游盒通过PUBG玩家社区收到了关于未授权程序的举报,举报称有人在游戏内使用了能测量玩家间的距离并辅助迫击炮射击的未授权辅助程序。为此,我们想就该事项向大家进行如下公告: 使用此类未授权程序的行为违反…

23/76-LeNet

LeNet 早期成功的神经网络。 先使用卷积层来学习图片空间信息。 然后使用全连接层转换到类别空间。 #In[]LeNet,上世纪80年代的产物,最初为了手写识别设计from d2l import torch as d2l import torch from torch import nn from torch.nn.modules.loss import CrossEntropyLos…

工业平板定制方案_基于联发科、紫光展锐平台的工业平板电脑方案

工业平板主板采用联发科MT6762平台方案,搭载Android 11.0操作系统, 主频最高2.0GHz,效能有大幅提升;采用12nm先进工艺,具有低功耗高性能的特点。 该工业平板主板搭载了IMG GE8320图形处理器,最高主频为680MHz, 支持108…

Flume 之自定义Sink

1、简介 前文我们介绍了 Flume 如何自定义 Source, 并进行案例演示,本文将接着前文,自定义Sink,在这篇文章中,将使用自定义 Source 和 自定义的 Sink 实现数据传输,让大家快速掌握Flume这门技术。 2、自定…

Python - 深夜数据结构与算法之 Sort

目录 一.引言 二.排序简介 1.排序类型 2.时间复杂度 3.初级排序 4.高级排序 A.快速排序 B.归并排序 C.堆排序 5.特殊排序 三.经典算法实战 1.Quick-Sort 2.Merge-Sort 3.Heap-Sort 4.Relative-Sort-Array [1122] 5.Valid-anagram [242] 6.Merge-Intervals […

Java NIO (二)NIO Buffer类的重要方法(备份)

1 allocate()方法 在使用Buffer实例前,我们需要先获取Buffer子类的实例对象,并且分配内存空间。需要获取一个Buffer实例对象时,并不是使用子类的构造器来创建,而是调用子类的allocate()方法。 public class AllocateTest {static…

如何快速看懂一篇英文AI论文?

已经2024年了,该出现一个写论文解读AI Agent了。 大家肯定也在经常刷论文吧。 但真正尝试过用GPT去刷论文、写论文解读的小伙伴,一定深有体验——费劲。其他agents也没有能搞定的,今天我发现了一个超级厉害的写论文解读的agent &#xff0c…

某银行主机安全运营体系建设实践

随着商业银行业务的发展,主机规模持续增长,给安全团队运营工作带来极大挑战,传统的运营手段已经无法适应业务规模的快速发展,主要体现在主机资产数量多、类型复杂,安全团队难以对全量资产进行及时有效的梳理、管理&…

HCIA—— 16每日一讲:HTTP和HTTPS、无状态和cookie、持久连接和管线化、(初稿丢了,这是新稿,请宽恕我)

学习目标: HTTP和HTTPS、无状态和cookie、持久连接和管线化、HTTP的报文、URI和URL(初稿丢了,这是新稿,请宽恕我😶‍🌫️) 学习内容: HTTP无状态和cookieHTTPS持久连接和管线化 目…

vue2 pdfjs-2.8.335-dist pdf文件在线预览功能

1、首先先将 pdfjs-2.8.335-dist 文件夹从网上搜索下载,复制到public文件夹下. 2、在components下新建组件PdfViewer.vue文件 3、在el-upload 中调用 pdf-viewer 组件 4、在el-upload 中的 on-preview方法中加上对应的src路径 internalPreview(file) { //判断需要…

编译原理1.3习题 程序设计语言的发展历程

图源:文心一言 编译原理习题整理~🥝🥝 作为初学者的我,这些习题主要用于自我巩固。由于是自学,答案难免有误,非常欢迎各位小伙伴指正与讨论!👏💡 第1版:自…