java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java IO模型、Netty原理

Java的IO模型、Netty原理解析

作者:卷福同学

Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇文章主要介绍了Java的IO模型、Netty原理详解,需要的朋友可以参考下

1.什么是IO

虽然作为Java开发程序员,很多都听过IO、NIO这些,但是很多人都没深入去了解这些内容。

2.同步与异步、阻塞与非阻塞

3.三种IO模型

BIO(Blocking I/O)

同步并阻塞模式,调用方在发起IO操作时会被阻塞,直到操作完成才能继续执行,适用于连接数较少的场景。

例如:服务端通过ServerSocket监听端口,accept()阻塞等待客户端连接。

优缺点:

适用于低并发、短连接的场景,如传统的HTTP服务

NIO(Non-blocking I/O)

同步非阻塞模型,客户端发送的连接请求都会注册到Selector多路复用器上,服务器端通过Selector管理多个通道Channel,Selector会轮询这些连接,当轮询到连接上有IO活动就进行处理。

NIO基于 Channel 和 Buffer 进行操作,数据总是从通道读取到缓冲区或者从缓冲区写入到通道。Selector 用于监听多个通道上的事件(比如收到连接请求、数据达到等等),因此使用单个线程就可以监听多个客户端通道。

IO多路复用:一个线程可对应多个连接,不用为每个连接都创建一个线程

核心组件:

优缺点:

使用于高并发、长连接的场景,如即时通讯场景

AIO(Asynchronous I/O)

异步非阻塞模型,基于事件回调或Future机制

优缺点:

适用于高吞吐、低延迟的场景,如日志批量写入

4.什么是Netty

说起Java的IO模型,绕不开的就是Netty框架了,那什么是Netty,为什么Netty的性能这么高呢?

很多中间件的底层通信框架用的都是它,比如:RocketMQ、Dubbo、Elasticsearch

4.1 Netty的核心要点

核心特点:

高性能的核心原因:

4.2 零拷贝技术

Netty的零拷贝体现在操作数据时, 不需要将数据 buffer从 一个内存区域拷贝到另一个内存区域。少了一次内存的拷贝,CPU 效率就得到的提升。

4.2.1 Linux系统的文件从本地磁盘发送到网络中的零拷贝技术

4.2.2 Netty零拷贝技术

4.2.3 Netty和操作系统的零拷贝的区别?

Netty 的 Zero-copy 完全是在用户态(Java 应用层)的, 更多的偏向于优化数据操作。而在 OS 层面上的 Zero-copy 通常指避免在用户态(User-space)与内核态(Kernel-space)之间来回拷贝数据

4.3 Reactor模式

Reactor可以分为单Reactor单线程模式、单Reactor多线程模型,主从Reactor多线程模型

4.3.1 单Reactor单线程模式

该模式简单,所有操作都由1个IO线程处理,缺点是存在性能瓶颈,只有1个线程工作,无法发挥多核CPU的性能。

4.3.2 单Reactor多线程模式

充分发挥了多核CPU的处理能力,缺点是用一个线程接收事件和响应,高并发时仍然会有性能瓶颈

4.3.3 主从Reactor多线程模式

该模式优点是主从线程分工明确,能应对更高的并发。缺点是编程复杂度较高。

应用该模式的中间件有:Dubbo、RocketMQ、Zookeeper

小结

Reactor模式的核心在于用一个或少量线程来监听多个连接上的事件,根据事件类型分发调用相应处理逻辑,从而避免为每个连接都分配一个线程

4.4 Netty的线程模型

简单理解:Boss线程是老板,Worker线程是员工,老板负责接收处理的事件请求,Worker负责工作,处理请求的I/O事件,并交给对应的Handler处理

本质是将线程连接和具体的业务处理分开

5.多路复用I/O的3种机制

5.1 select

这三种都是操作系统中的多路复用I/O机制

轮询机制:select使用一个固定大小的位图来表示文件描述符集,将文件描述符的状态(如可读、可写)存储在一个数组中,调用select时,每次需将完整的位图从用户空间拷贝到内核空间,内核遍历所有描述符,检查就绪状态

局限:

5.2 poll

poll使用了动态数组来替代位图,使用pollfd结构数组存储文件描述符和事件,无数量限制

工作机制:每次调用时仍然需要遍历所有描述符,即使只有少量描述符修改了,仍然要检查整个数组,时间复杂度为O(N)

5.3 epoll

1)事件驱动模型:epoll使用红黑树来存储和管理注册的文件描述符,使用就绪事件链表来存储触发的事件。当某个文件描述符上的事件就绪时,epoll会将该文件描述符添加到就绪链表中。

2)触发模式:支持水平触发(LT)和边缘触发(ET),ET模式下事件仅通知一次

3)工作流程

优点是时间复杂度O(1),仅处理活跃连接,性能和连接数无关

4)零拷贝机制

总结:epoll每次只传递发生的事件,不需要传递所有文件描述符,所以提高了效率

6. Netty如何解决JDK NIO空轮询bug的?

Java NIO在Linux系统下默认是epoll机制,理论上无客户端连接时Selector.select()方法是会阻塞的。

发生空轮询bug表现时,即时select轮询事件返回数量是0,Select.select()方法也不会被阻塞,NIO就会一直处于while死循环中,不断向CPU申请资源导致CPU 100%

底层原因

6.1 Netty的解决方式

Netty并没有解决这个bug,而是绕开了这个错误,具体如下:

1)统计空轮询次数:通过selectCnt计数器来统计连续空轮询的次数,每次执行Selector.select()方法后,如果发现没有IO事件,selectCnt就会递增

2)设置阈值:定义了一个阈值,默认为512,当空轮询达到这个阈值时,Netty就会触发重建Selector的操作

3)重建Selector:Netty新建一个Selector,并将所有注册的Channel从旧的Selector转移到新的Selector上,过程涉及取消旧Selector上的注册,以及新Selector上重新注册

4)关闭旧的Selector:重建Selector并将Channel重新注册后,Netty关闭旧的Selector

总结:通过SelectCnt统计没有IO事件的次数,来判断当前是否发生了空轮询,如果发生了,就重建一个Selector来替换之前出问题的Selector

核心代码如下:

long time = System.nanoTime();
//调用select方法,阻塞时间为上面算出的最近一个将要超时的定时任务时间
int selectedKeys = selector.select(timeoutMillis);
//计数器加1
++selectCnt;
if (selectedKeys != 0 || oldWakenUp || this.wakenUp.get() || this.hasTasks() || this.hasScheduledTasks()) {
   //进入这个分支,表示正常场景     
   //selectedKeys != 0: selectedKeys个数不为0, 有io事件发生
   //oldWakenUp:表示进来时,已经有其他地方对selector进行了唤醒操作
   //wakenUp.get():也表示selector被唤醒
   //hasTasks() || hasScheduledTasks():表示有任务或定时任务要执行
   //发生以上几种情况任一种则直接返回
   break;
}
//此处的逻辑就是: 当前时间 - 循环开始时间 >= 定时select的时间timeoutMillis,说明已经执行过一次阻塞select(), 有效的select
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
   //进入这个分支,表示超时,属于正常的场景
   //说明发生过一次阻塞式轮询, 并且超时
   selectCnt = 1;
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
   //进入这个分支,表示没有超时,同时 selectedKeys==0
   //属于异常场景
   //表示启用了select bug修复机制,
   //即配置的io.netty.selectorAutoRebuildThreshold
   //参数大于3,且上面select方法提前返回次数已经大于
   //配置的阈值,则会触发selector重建
   //进行selector重建
   //重建完之后,尝试调用非阻塞版本select一次,并直接返回
   selector = this.selectRebuildSelector(selectCnt);
   selectCnt = 1;
   break;
}
currentTimeNanos = time;

到此这篇关于Java的IO模型、Netty原理详解的文章就介绍到这了,更多相关Java IO模型、Netty原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文