Netty零拷贝

发布时间 2023-05-29 23:54:19作者: 彷佛昨天

传统读取IO流的操作

读操作

1、应用程序发起读数据操作,JVM会发起read()系统调用。

2、这时操作系统OS会进行一次上下文切换(把用户空间切换到内核空间)

3、通过磁盘控制器把数据copy到内核缓冲区中,这里的就发生了一次DMA Copy

4、然后内核将数据copy到用户空间的应用缓冲区中,发生了一次CPU Copy

5read调用返回后,会再进行一次上下文切换(把内核空间切换到用户空间)

读操作发送两次数据拷贝、两次上下文的切换。

写操作

1、应用发起写操作,OS进行一次上下文切换(从用户空间切换为内核空间)

2、并且把数据copy到内核缓冲区Socket Buffer,做了一次CPU Copy

3、内核空间再把数据copy到磁盘或其他存储(网卡,进行网络传输),进行了DMA Copy

4、写入结束后返回,又从内核空间切换到用户空间

两次上下文切换 两次数据拷贝

也就是说 socket客户端与服务器端通讯可能会发生四次上下文切换 四次数据拷贝。

 

MMAP+write

 

read() 系统调用的过程中会把内核缓冲区的数据拷贝到用户的缓冲区里,于是为了减少这一步开销,我们可以用 mmap() 替换 read() 系统调用函数。

 

mmap() 系统调用函数会直接把内核缓冲区里的数据「映射」到用户空间,这样,操作系统内核与用户空间就不需要再进行任何的数据拷贝操作。

 

可以减少一次 内核空间拷贝数据到用户空间,该方案也不是最理想的零拷贝实现方案

 

 

 

 

 

 

Linux内核 在2.4版本:

 

sendfile() 系统调用的过程发生了点变化

 

MA 控制器就可以直接将内核缓存中的数据拷贝到网卡的缓冲区里,此过程不需要将数据从操作系统内核缓冲区拷贝到 socket 缓冲区中,这样就减少了一次数据拷贝;这就是所谓的零拷贝(Zero-copy)技术

 

因为我们没有在内存层面去拷贝数据,也就是说全程没有通过 CPU 来搬运数据,所有的数据都是通过 DMA 来进行传输的,总体来看,零拷贝技术可以把文件传输的性能提高至少一倍以上。

 

 

Netty线程模型

 

单线程模型 (Reactor单线程)

多线程模型 (Reactor多线程)

主从多线程模型 (Reactor多线程)

 

Reactor单线程

 

 

所有操作都在同一个NIO线程处理,在这个单线程中要负责接收请求,处理IO,编解码所有操作,相当于一个饭馆只有一个人,同时负责前台和后台服务,效率低。

优点:不需要上下文切换 、不存在线程安全的问题

缺点:只有一个线程处理,无法发挥cpu多核的效率  如果请求比较多的情况下,容易遇到瓶颈

 

Reactor多线程

 

相当于一个饭馆有一个前台负责接待,有很多服务员去做后面的工作,这样效率就比单线程模型提高很多。

相关代码

EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(2);

    ServerBootstrap bootstrap = new ServerBootstrap();

  

Reactor主从模型

 

多线程模型的缺点在于并发量很高的情况下,只有一个Reactor单线程去处理是来不及的,就像饭馆只有一个前台接待很多客人也是不够的。为此需要使用主从线程模型。

 

主从线程模型:一组线程池接收请求,一组线程池处理IO

 

 

1.服务端使用一个独立的主Reactor线程池来处理客户端连接,当服务端收到连接请求时,从主线程池中随机选择一个Reactor线程作为Acceptor线程处理连接;

2.链路建立成功后,将新创建的SocketChannel注册到sub reactor线程池的某个Reactor线程上,由它处理后续的I/O操作。

1.mainReactor负责监听server socket,用来处理新连接的建立,将建立的socketChannel指定注册给subReactor

2.subReactor维护自己的selector, 基于mainReactor 注册的socketChannel多路分离IO读写事件,读写网 络数据,对业务处理的功能,另其扔给worker线程池来完成。

 

 通俗易懂理解为:mainReactor为老板、只负责接口、subReactor负责前台接待 也就是多个前台接待。

 

比起多线程单 Rector 模型,它是将 Reactor 分成两部分,mainReactor 负责监听并 Accept新连接,然后将建立的 socket 通过多路复用器(Acceptor)分派给subReactorsubReactor 负责多路分离已连接的 socket,读写网络数据;业务处理功能,其交给 worker 线程池完成。通常,subReactor 个数上可与 CPU 个数等同。