76 netty零拷贝原理&netty线程模型原理

1 什么是零拷贝 zery-copy

2,为什么kafka中使用 zery-copy

3, 零拷贝技术实现方案有哪些?

4,mmap 与 sendfile 之间的区别

5, Reactor 三种线程模型

6,单个reactor,单个 reactor 多线程,主从reactor多线程

7, netty 线程模型原理
内存池 netty线程模型源码

传统的读取IO操作:
读操作:
1,应用程序发起读数据的操作,JVM会发起read()系统调用
2,这时候操作系统OS 会进行一次上下文切换(把用户空间切换到讷河空间)
3.通过磁盘控制器把数据copy到内核缓冲区内,这里就发生了一次DMA Copy
4, 然后内核将数据copy到用户空间的应用缓冲区内,发生了一次CPU Copy
5, read 调用返回后,会在进行一次上下文切换(内核空间切换到用户空间)

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

1、应用发起写操作,OS进行一次上下文切换(从用户空间切换为内核空间)
2、并且把数据copy到内核缓冲区Socket Buffer,做了一次CPU Copy
3、内核空间再把数据copy到磁盘或其他存储(网卡,进行网络传输),进行了DMA Copy
4、写入结束后返回,又从内核空间切换到用户空间
两次上下文切换 两次数据拷贝

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

zero-copy 零拷贝:
1,减少cpu拷贝数据次数, 最好不要让我们的cpu拷贝数据
2,减少cpu上下文切换 用户与内核态之间的切换。

1,直接内存 使用dma控制器将本地硬盘的数据拷贝到内核态,可以减少一次数据拷贝。

传统IO为什么非常慢:

服务器下载文件:
1,从本地硬盘读取该文件;
2,在将该文件写入到网卡里;

用户与内核态之间的切换非常的频繁,效率很低。
步骤:
1,从本地硬盘读取该文件:
用户空间发出read 方法到内核态,内核态读取硬盘数据
用户态切换到内核态,使用dma技术将硬盘的数据拷贝到内核态
内核态读取到数据后,cpu从内核态将该数据拷贝到用户态。

上下文切换2次 cpu数据拷贝1次 dma数据拷贝一次,将该文件流缓存到用户空间

2,再将该文件写入到网卡中

用户发出write方法,将该数据写入到网卡中
用户态切换到内核态,使用cpu线程将该数据拷贝到内核态socket缓冲。
使用dma技术奖数据拷贝到网卡中。
内核态切换到用户态。
上下文切换2次 cpu数据拷贝1次 dma数据拷贝一次
得出结论: 服务器下载 上下文切换4次 cpu数据拷贝2次 dma数据拷贝2次

mmap与sendfile零拷贝原理:

MMAP+write
read() 系统调用的过程中会把内核缓冲区的数据拷贝到用户的缓冲区内,于是为了减少这一步的开销,我们可以使用mmap()替换read()系统调用函数,
mmap()系统调用函数会直接把内核缓冲区里的数据(映射)到用户空间,这样,操作系统内核与用户空间就不需要再进行任何的数据拷贝工作。


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

Sendfile

LInux 内核 在2.1版本

可以直接把内核缓冲区里的数据拷贝到socket缓冲区里,不在拷贝到用户态,这样就只有2次上下文切换,和3次数据拷贝 ,但是这还不是真正的零拷贝技术

Linux 内核 在2.4版本
sendfile() 系统调用的过程发生了点变化
MA控制器就可以直接将内核缓冲区的数据拷贝到网卡的缓冲区内,此过程不需要将数据从操作系统内核缓冲区拷贝到socket 缓冲区中,这样就减少了一次数据拷贝;这就是所谓的零拷贝(Zero-copy)技术:

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

零拷贝应用场景
1,kafka
2,Nginx
3,Spark
netty线程模型

事件驱动:
可以处理一个或者多个输入源(one or more inputs)
通过Service Handler 同步的将输入事件(Event
)采用多路复用分发给相应的Request handler(多个)处理。
单线程模型(单Reactor单线程)
多线程模型: (单Reactor 多线程)
主从多线程模型:(多Reactor 多线程)

传统的IO 模型
单Reactor 单线程。



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

  1. 一个NIO线程同时处理成百上千的连接,性能上无法支撑,即便NIO线程的CPU负荷达到100%,也无法满足海量消息的编码、解码、读取和发送。
    2.当NIO线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重了NIO线程的负载,最终会导致大量消息积压和处理超时,成为系统的性能瓶颈。
    3.可靠性问题:一旦NIO线程意外跑飞,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障。
    使用场景:客户端发送连接数量有限,且业务线程处理时间非常快 可以使用单个线程维护多个不同连接 ,代表有Redis。
Netty相关代码
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
ServerBootstrap bootstrap = new ServerBootstrap();


优点:
不需要上下文切换,不存在线程安全的问题
缺点:、1,只有一个线程处理,无法发挥cpu多核的效率
2,如果请求较多的情况下容易遇到瓶颈,
3,如果某种意外的情况下因线程终止了,导致整个系统木块无法使用
适用于客户端连接数量有一定的限制,业务处理时间非常快
单Reactor 多线程



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

1.由一个Reactor线程-Acceptor线程用于监听服务端,接收客户端连接请求;
2.网络I/O操作读、写等由Reactor线程池负责处理;
3.一个Reactor线程可同时处理多条链路,但一条链路只能对应一个Reactor线程,这样可避免并发操作问题;
4.绝大多数场景下,Reactor多线程模型都可以满足性能需求,但是,在极个别特殊场景中,一个Reactor线程负责监听和处理所有的客户端连接可能会存在性能问题。例如并发百万客户端连接,或者服务端需要对客户端握手进行安全认证,但是认证本身非常损耗性能。因此,诞生了第三种线程模型;
Netty设置相关代码

EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();

创建一个线程的bossGroup 线程组,这个线程负责处理客户端的连接请求,而workerGroup 默认使用处理器个数*2的线程数量来处理I/o操作这就是相当于Reactor的多线程模型
优点:
可以充分利用多线程多核处理能力,处理效率比单线程快
缺点:
对线程之间访问数据,有可能遇到线程安全问题。

REactor 主从模型:
多线程模型的缺点在于并发量很高的情况下,只有一个REactor 单线程去处理是来不及的,就像饭馆只有一个前台接待很多客人也是不够的。‘为此需要使用主从线程模型。
主从线程模型: 一个线程池接收请求,一组线程池处理IO
1,服务端使用一个独立的主Reactor 线程池来处理客户端连接,党服务端收到连接请求时,从主线程池里随机选择有个Reactor 线程池来处理客户端的连接
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)分派给subReactor。subReactor 负责多路分离已连接的 socket,读写网络数据;业务处理功能,其交给 worker 线程池完成。通常,subReactor 个数上可与 CPU 个数等同。
Netty线程模型
1.创建服务端的时候实例化了 2 个 EventLoopGroup。bossGroup 线程组实际就是 Acceptor 线程池,负责处理客户端的 TCP 连接请求。workerGroup 是真正负责 I/O 读写操作的线程组。通过这里能够知道 Netty 是多 Reactor 模型。

2.ServerBootstrap 类是 Netty 用于启动 NIO 的辅助类,能够方便开发。通过 group 方法将线程组传递到 ServerBootstrap 中,设置 Channel 为 NioServerSocketChannel,接着设置 NioServerSocketChannel 的 TCP 参数,最后绑定 I/O 事件处理类 ChildChannelHandler。
辅助类完成配置之后调用 bind 方法绑定监听端口,Netty 返回 ChannelFuture,f.channel().closeFuture().sync() 对同步阻塞的获取结果。

3.调用线程组 shutdownGracefully 优雅推出,释放资源。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章