Netty-NIO三大组件
本文最后更新于:2 年前
channel:双向数据传输通道
filechannel:文件
datagramchannel:
socketchannel:
serversocketchannel:
buffer:内存缓冲区
bytebuffer:
selector
选择器?
服务器端早期–多个客户端连接==多线程:
缺点:
内存占用高。
线程上下文切换成本高。
只适合连接数较少。
线程池版本设计:
缺点:
线程同一时间只能处理一个 socket。
仅适合短连接(长连接–一直保持连接)场景。
selector 设计:
一个线程管理多个 channel。
调用 selector 的 select() 会阻塞直到 channel 发生了读写就绪事件,这些事件发生,select 方法就会返回这些事件交给 thread 来处理。
基本使用
向 buffer 写入数据,例如调用 channel.read(buffer)
调用 flip() 切换至读模式
从 buffer 读取数据,例如调用 buffer.get()
调用 clear() 或 compact() 切换至写模式
重复 1~4 步骤
分配空间:
bytebuffer.allocate().==堆内存=读写效率低=受到 GC 影响
Bytebuffer.allocatedirect()==直接内存=读写效率高=分配内存的效率低
写入:write()
读取:get()
get 方法会让 position 读指针向后走,如果想重复读取数据
可以调用 rewind 方法将 position 重新置为 0
调用 get(int i) 方法获取索引 i 的内容,它不会移动读指针
mark 和 reset
mark 是在读取时,做一个标记,即使 position 改变,只要调用 reset 就能回到 mark 的位置
rewind 和 flip 都会清除 mark 位置
概念
阻塞 IO(同步):
非阻塞 IO(同步):read 为例,在等待数据阶段用户线程不阻塞(多次内存空间的切换)。复制数据阶段阻塞。
多路复用(同步):select()为例,两个阶段都是阻塞的。好处在于:可以一次性的处理多个 channel 上的事件。
信号驱动:不常用
异步 IO:
异步(线程自己不去获得结果,而是由其他的线程送来结果)
异步情况下一定是非阻塞的。
异步意味着:在进行读写操作时,线程不必等待结果,而是通过回调的方式由另外的线程来获取。
linux 在 2.6 底层通过多路复用模拟了异步 IO。
windows 通过 IOCP 真正实现了异步 IO。
netty 废弃了异步 IO。
零拷贝
4 次数据拷贝
用户态内核态切换三次
NIO 优化
通过 DirectByteBuf
ByteBuffer.allocate(10) HeapByteBuffer 使用的还是 java 内存
ByteBuffer.allocateDirect(10) DirectByteBuffer 使用的是操作系统内存
java 可以使用 DirectByteBuf 将堆外内存映射到 jvm 内存中来直接访问使用这块内存不受 jvm 垃圾回收的影响,因此内存地址固定,有助于 IO 读写
java 中的 DirectByteBuf 对象仅维护了此内存的虚引用,内存回收分成两步
- DirectByteBuf 对象被垃圾回收,将虚引用加入引用队列
- 通过专门线程访问引用队列,根据虚引用释放堆外内存
减少了一次数据拷贝,用户态与内核态的切换次数没有减少
进一步(linux2.1 之后的 sendfile 方法)
java 中对应着两个 channel 调用 transferTo/transferFrom 方法拷贝数据
- java 调用 transferTo 方法后,要从 java 程序的用户态切换至内核态,使用 DMA将数据读入内核缓冲区,不会使用 cpu
- 数据从内核缓冲区传输到 socket 缓冲区,cpu 会参与拷贝
- 最后使用 DMA 将 socket 缓冲区的数据写入网卡,不会使用 cpu
只发生了一次用户态与内核态的切换
数据拷贝了 3 次
进一步(linux2.4 之后的 sendfile 方法)
- java 调用 transferTo 方法后,要从 java 程序的用户态切换至内核态,使用 DMA 将数据读入内核缓冲区,不会使用 cpu
- 只会将一些 offset 和 length 信息拷入 socket 缓冲区,几乎无消耗
- 使用 DMA 将内核缓冲区的数据写入网卡,不会使用 cpu
整个过程仅只发生了一次用户态与内核态的切换,数据拷贝了 2 次。
所谓的【零拷贝】=linux=sendfile 方法,并不是真正无拷贝,而是在不会拷贝重复数据到 jvm 内存中。
零拷贝的优点有
- 更少的用户态与内核态的切换
- 不利用 cpu 计算,减少 cpu 缓存伪共享(使用 DMA 硬件)
- 零拷贝适合小文件传输=大文件没有缓冲的作用。(缓冲区比较小)