草庐IT

RPC设计应该使用哪种网络IO模型?

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

1 常见网络I/O模型

两台PC机之间网络通信,就是两台PC机对网络IO的操作。

同步阻塞IO、同步非阻塞IO(NIO)、IO多路复用和异步非阻塞IO(AIO)。只有AIO为异步IO,其他都是同步IO。

1.1 同步阻塞I/O(BIO)

Linux默认所有socket都是blocking。

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

系统内核处理IO操作分为两阶段:

  • • 等待数据系统内核在等待网卡接收到数据后,把数据写到内核中
  • • 拷贝数据系统内核在获取到数据后,将数据拷贝到用户进程的空间
在这两个阶段,应用进程中IO操作的线程会一直都处于阻塞状态,若基于Java多线程开发,每个IO操作都要占用线程,直至IO操作结束。

用户线程发起read调用后就阻塞了,让出CPU。内核等待网卡数据到来,把数据从网卡拷贝到内核空间,接着把数据拷贝到用户空间,再把用户线程叫醒。

1.2 IO多路复用(IO multiplexing)

高并发场景中使用最为广泛的一种IO模型,如Java的NIO、Redis、Nginx的底层实现就是此类IO模型的应用:

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

当用户进程发起select调用,进程会被阻塞,当发现该select负责的socket有准备好的数据时才返回,之后才发起一次read,整个流程比阻塞IO要复杂,似乎更浪费性能。但最大优势在于,用户可在一个线程内同时处理多个socket的IO请求。用户可注册多个socket,然后不断调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程实现。

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

本质上多路复用还是同步阻塞。

1.3 为何阻塞IO,IO多路复用最常用?

网络IO的应用上,需要的是系统内核的支持及编程语言的支持。

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

无论C++还是Java,在高性能的网络编程框架都是基于Reactor模式,如Netty,Reactor模式基于IO多路复用。非高并发场景,同步阻塞IO最常见。

应用最多的、系统内核与编程语言支持最为完善的,便是阻塞IO和IO多路复用,满足绝大多数网络IO应用场景。

1.4 RPC框架选择哪种网络IO模型?

IO多路复用适合高并发,用较少进程(线程)处理较多socket的IO请求,但使用难度较高。

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

RPC调用大多数是高并发调用,综合考虑,RPC选择IO多路复用。最优框架选择即基于Reactor模式实现的框架Netty。Linux下,也要开启epoll提升系统性能。

2 零拷贝(Zero-copy)

2.1 网络IO读写流程

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

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

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

2.2 实现

是不是用户空间与内核空间都将数据写到一个地方,就不需要拷贝了?想到虚拟内存吗?

虚拟内存

零拷贝有两种实现:

mmap+write

通过虚拟内存来解决。

sendfile

Nginx sendfile

3 Netty零拷贝

RPC框架在网络通信框架的选型基于Reactor模式实现的框架,如Java首选Netty。那Netty有零拷贝机制吗?Netty框架中的零拷贝和我之前讲的零拷贝又有什么不同呢?

上节的零拷贝是os层的零拷贝,为避免用户空间与内核空间之间的数据拷贝操作,可提升CPU利用率。

而Netty零拷贝不大一样,他完全站在用户空间,即JVM上,偏向于数据操作的优化。

Netty这么做的意义

传输过程中,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 方式在原理一样。

4 总结

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

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

有关RPC设计应该使用哪种网络IO模型?的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  9. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  10. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

随机推荐