netty 文件分发,netty详细步骤

首页 > 实用技巧 > 作者:YD1662024-02-21 13:52:34

数据读写的发展历程2

2.1 传统数据读写

初学 Java 时,我们在学习 IO 和 网络编程时,会使用以下代码:

File file = new File("index.html"); RandomAccessFile raf = new RandomAccessFile(file, "rw"); byte[] arr = new byte[(int) file.length()]; raf.read(arr); Socket socket = new ServerSocket(8080).accept(); socket.getOutputStream().write(arr);

我们会调用 read 方法读取 index.html 的内容—— 变成字节数组,然后调用 write 方法,将 index.html 字节流写到 socket 中,那么,我们调用这两个方法,在 OS 底层发生了什么呢?如上图最左边的流程:

read 调用导致用户态到内核态的一次变化,同时,第一次复制开始:DMA(Direct Memory Access,直接内存存取,即不使用 CPU 拷贝数据到内存,而是 DMA 引擎传输数据到内存,用于解放 CPU) 引擎从磁盘读取数据,并将数据放入到内核缓冲区。

发生第二次数据拷贝,即:将内核缓冲区的数据拷贝到用户缓冲区,同时,发生了一次用内核态到用户态的上下文切换。

发生第三次数据拷贝,我们调用 write 方法,系统将用户缓冲区的数据拷贝到 Socket 缓冲区。此时,又发生了一次用户态到内核态的上下文切换。

第四次拷贝,数据异步的从 Socket 缓冲区,使用 DMA 引擎拷贝到网络协议引擎。这一段,不需要进行上下文切换。

write 方法返回,再次从内核态切换到用户态。

2.2 mmap优化

mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。

如上图中间的流程,user buffer 和 kernel buffer 共享 数据。如果你想把硬盘的数据传输到网络中,只需要从内核缓冲区拷贝到 Socket 缓冲区即可,比传统read write方式减少了两次CPU Copy操作。但不减少上下文切换次数。

2.3 sendfile方式

sendfile系统调用在内核版本2.1中被引入,目的是简化通过网络在两个通道之间进行的数据传输过程。如上图最右边的流程,sendfile数据传送只发生在内核空间,所以减少了一次上下文切换;但是还是存在一次copy,能不能把这一次copy也省略掉,Linux2.4内核中做了改进,将Kernel buffer中对应的数据描述信息(内存地址,偏移量)记录到相应的socket缓冲区当中,这样连内核空间中的一次cpu copy也省掉了。

2.4 Netty的零拷贝

Netty 的“零拷贝”主要体现在如下三个方面:

Netty 的接收和发送 ByteBuffer 采用 DIRECT BUFFERS,使用堆外直接内存进行 Socket 读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行 Socket 读写,JVM 会将堆内存 Buffer 拷贝一份到直接内存中,然后才写入 Socket 中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。

Netty 提供了组合 Buffer 对象,可以聚合多个 ByteBuffer 对象,用户可以像操作一个 Buffer 那样方便的对组合 Buffer 进行操作,避免了传统通过内存拷贝的方式将几个小 Buffer 合并成一个大的 Buffer。

Netty 的文件传输采用了 transferTo 方法,它可以直接将文件缓冲区的数据发送到目标 Channel,避免了传统通过循环 write 方式导致的内存拷贝问题。

三、内存池

随着 JVM 虚拟机和 JIT 即时编译技术的发展,对象的分配和回收是个非常轻量级的工作。但是对于缓冲区 Buffer,情况却稍有不同,特别是对于堆外直接内存的分配和回收,是一件耗时的操作。为了避免频繁的内存分配给系统带来负担以及GC对系统性能带来波动,Netty4提出了基于内存池的缓冲区重用机制,使用全新的内存池来管理内存的分配和回收。

(此处我还没有看明白,先做留白~~后续继续完善。)

四、高效的Reactor多线程模型4.1 事件驱动模型

netty 文件分发,netty详细步骤(13)

netty 文件分发,netty详细步骤(14)

事件驱动方式:发生事件,主线程把事件放入事件队列,在另外线程不断循环消费事件列表中的事件,调用事件对应的处理逻辑处理事件。事件驱动方式也被称为消息通知方式,其实是设计模式中观察者模式的思路。

主要包括 4 个基本组件:

事件队列(event queue):接收事件的入口,存储待处理事件;

分发器(event mediator):将不同的事件分发到不同的业务逻辑单元;

事件通道(event channel):分发器与处理器之间的联系渠道;

事件处理器(event processor):实现业务逻辑,处理完成后会发出事件,触发下一步操作。

可以看出,相对传统轮询模式,事件驱动有如下优点:

可扩展性好:分布式的异步架构,事件处理器之间高度解耦,可以方便扩展事件处理逻辑;

高性能:基于队列暂存事件,能方便并行异步处理事件。

4.2 Reactor线程模型

netty 文件分发,netty详细步骤(15)

Reactor 是反应堆的意思,Reactor 模型是指通过一个或多个输入同时传递给服务处理器的服务请求的事件驱动处理模式。

Reactor 模型中有 2 个关键组成:

Reactor:Reactor 在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对 IO 事件做出反应。它就像公司的电话接线员,它接听来自客户的电话并将线路转移到适当的联系人;

Handlers:处理程序执行 I/O 事件要完成的实际事件,类似于客户想要与之交谈的公司中的实际官员。Reactor 通过调度适当的处理程序来响应 I/O 事件,处理程序执行非阻塞操作。

取决于 Reactor 的数量和 Hanndler 线程数量的不同,Reactor 模型有 3 个变种:

1)单 Reactor 单线程;

2)单 Reactor 多线程;

3)主从 Reactor 多线程。

Netty 主要基于主从 Reactors 多线程模型(如下图)做了一定的修改,其中主从 Reactor 多线程模型有多个 Reactor:

netty 文件分发,netty详细步骤(16)

上一页12345下一页

栏目热文

文档排行

本站推荐

Copyright © 2018 - 2021 www.yd166.com., All Rights Reserved.