Java 开发必备! I/O与Netty原理精讲(建议收藏)

Posted Javachichi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 开发必备! I/O与Netty原理精讲(建议收藏)相关的知识,希望对你有一定的参考价值。

I/O技术在系统设计、性能优化、中间件研发中的使用越来越重要,学习和掌握I/O相关技术已经不仅是一个Java攻城狮的加分技能,而是一个必备技能。本文将带你了解BIO/NIO/AIO的发展历程及实现原理,并介绍当前流行框架Netty的基本原理。

一 Java I/O模型

1 BIO(Blocking IO)

BIO是同步阻塞模型,一个客户端连接对应一个处理线程。在BIO中,accept和read方法都是阻塞操作,如果没有连接请求,accept方法阻塞;如果无数据可读取,read方法阻塞。

2 NIO(Non Blocking IO)

NIO是同步非阻塞模型,服务端的一个线程可以处理多个请求,客户端发送的连接请求注册在多路复用器Selector上,服务端线程通过轮询多路复用器查看是否有IO请求,有则进行处理。

NIO的三大核心组件:

Buffer:用于存储数据,底层基于数组实现,针对8种基本类型提供了对应的缓冲区类。

Channel:用于进行数据传输,面向缓冲区进行操作,支持双向传输,数据可以从Channel读取到Buffer中,也可以从Buffer写到Channel中。

Selector:选择器,当向一个Selector中注册Channel后,Selector 内部的机制就可以自动不断地查询(Select)这些注册的Channel是否有已就绪的 I/O 事件(例如可读,可写,网络连接完成等),这样程序就可以很简单地使用一个线程高效地管理多个Channel,也可以说管理多个网络连接,因此,Selector也被称为多路复用器。当某个Channel上面发生了读或者写事件,这个Channel就处于就绪状态,会被Selector监听到,然后通过SelectionKeys可以获取就绪Channel的集合,进行后续的I/O操作。

Epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。

3 AIO(NIO 2.0)

AIO是异步非阻塞模型,一般用于连接数较多且连接时间较长的应用,在读写事件完成后由回调服务去通知程序启动线程进行处理。与NIO不同,当进行读写操作时,只需直接调用read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。

如果你觉得自己学习效率低,缺乏正确的指导,可以加入资源丰富,学习氛围浓厚的技术圈一起学习交流吧!
[Java架构群]
群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的JAVA交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

二 I/O模型演化

1 传统I/O模型

对于传统的I/O通信方式来说,客户端连接到服务端,服务端接收客户端请求并响应的流程为:读取 -> 解码 -> 应用处理 -> 编码 -> 发送结果。服务端为每一个客户端连接新建一个线程,建立通道,从而处理后续的请求,也就是BIO的方式。

这种方式在客户端数量不断增加的情况下,对于连接和请求的响应会急剧下降,并且占用太多线程浪费资源,线程数量也不是没有上限的,会遇到各种瓶颈。虽然可以使用线程池进行优化,但是依然有诸多问题,比如在线程池中所有线程都在处理请求时,无法响应其他的客户端连接,每个客户端依旧需要专门的服务端线程来服务,即使此时客户端无请求,也处于阻塞状态无法释放。基于此,提出了基于事件驱动的Reactor模型。

2 Reactor模型

Reactor模式是基于事件驱动开发的,服务端程序处理传入多路请求,并将它们同步分派给请求对应的处理线程,Reactor模式也叫Dispatcher模式,即I/O多路复用统一监听事件,收到事件后分发(Dispatch给某进程),这是编写高性能网络服务器的必备技术之一。

Reactor模式以NIO为底层支持,核心组成部分包括Reactor和Handler:

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

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

根据Reactor的数量和Handler线程数量,可以将Reactor分为三种模型:

  • 单线程模型 (单Reactor单线程)

  • 多线程模型 (单Reactor多线程)

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

单线程模型

Reactor内部通过Selector监控连接事件,收到事件后通过dispatch进行分发,如果是连接建立的事件,则由Acceptor处理,Acceptor通过accept接受连接,并创建一个Handler来处理连接后续的各种事件,如果是读写事件,直接调用连接对应的Handler来处理。

Handler完成read -> (decode -> compute -> encode) ->send的业务流程。

这种模型好处是简单,坏处却很明显,当某个Handler阻塞时,会导致其他客户端的handler和accpetor都得不到执行,无法做到高性能,只适用于业务处理非常快速的场景,如redis读写操作。

多线程模型

主线程中,Reactor对象通过Selector监控连接事件,收到事件后通过dispatch进行分发,如果是连接建立事件,则由Acceptor处理,Acceptor通过accept接收连接,并创建一个Handler来处理后续事件,而Handler只负责响应事件,不进行业务操作,也就是只进行read读取数据和write写出数据,业务处理交给一个线程池进行处理。

线程池分配一个线程完成真正的业务处理,然后将响应结果交给主进程的Handler处理,Handler将结果send给client。

单Reactor承担所有事件的监听和响应,而当我们的服务端遇到大量的客户端同时进行连接,或者在请求连接时执行一些耗时操作,比如身份认证,权限检查等,这种瞬时的高并发就容易成为性能瓶颈。

主从多线程模型

存在多个Reactor,每个Reactor都有自己的Selector选择器,线程和dispatch。

主线程中的mainReactor通过自己的Selector监控连接建立事件,收到事件后通过Accpetor接收,将新的连接分配给某个子线程。

子线程中的subReactor将mainReactor分配的连接加入连接队列中通过自己的Selector进行监听,并创建一个Handler用于处理后续事件。

Handler完成read -> 业务处理 -> send的完整业务流程。

关于Reactor,最权威的资料应该是Doug Lea大神的Scalable IO in Java,有兴趣的同学可以看看。

三 Netty线程模型

Netty线程模型就是Reactor模式的一个实现,如下图所示:

1 线程组

Netty抽象了两组线程池BossGroup和WorkerGroup,其类型都是NioEventLoopGroup,BossGroup用来接受客户端发来的连接,WorkerGroup则负责对完成TCP三次握手的连接进行处理。

NioEventLoopGroup里面包含了多个NioEventLoop,管理NioEventLoop的生命周期。每个NioEventLoop中包含了一个NIO Selector、一个队列、一个线程;其中线程用来做轮询注册到Selector上的Channel的读写事件和对投递到队列里面的事件进行处理。

Boss NioEventLoop线程的执行步骤:

  • 处理accept事件, 与client建立连接, 生成NiosocketChannel。

  • 将NioSocketChannel注册到某个worker NIOEventLoop上的selector。

  • 处理任务队列的任务, 即runAllTasks。

Worker NioEventLoop线程的执行步骤:

  • 轮询注册到自己Selector上的所有NioSocketChannel的read和write事件。

  • 处理read和write事件,在对应NioSocketChannel处理业务。

  • runAllTasks处理任务队列TaskQueue的任务,一些耗时的业务处理可以放入TaskQueue中慢慢处理,这样不影响数据在pipeline中的流动处理。

Worker NIOEventLoop处理NioSocketChannel业务时,使用了pipeline (管道),管道中维护了handler处理器链表,用来处理channel中的数据。

2 ChannelPipeline

Netty将Channel的数据管道抽象为ChannelPipeline,消息在ChannelPipline中流动和传递。ChannelPipeline持有I/O事件拦截器ChannelHandler的双向链表,由ChannelHandler对I/O事件进行拦截和处理,可以方便的新增和删除ChannelHandler来实现不同的业务逻辑定制,不需要对已有的ChannelHandler进行修改,能够实现对修改封闭和对扩展的支持。

ChannelPipeline是一系列的ChannelHandler实例,流经一个Channel的入站和出站事件可以被ChannelPipeline 拦截。每当一个新的Channel被创建了,都会建立一个新的ChannelPipeline并绑定到该Channel上,这个关联是永久性的;Channel既不能附上另一个ChannelPipeline也不能分离当前这个。这些都由Netty负责完成,而无需开发人员的特别处理。

根据起源,一个事件将由ChannelInboundHandler或ChannelOutboundHandler处理,ChannelHandlerContext实现转发或传播到下一个ChannelHandler。一个ChannelHandler处理程序可以通知ChannelPipeline中的下一个ChannelHandler执行。Read事件(入站事件)和write事件(出站事件)使用相同的pipeline,入站事件会从链表head 往后传递到最后一个入站的handler,出站事件会从链表tail往前传递到最前一个出站的 handler,两种类型的 handler 互不干扰。

ChannelInboundHandler回调方法:

ChannelOutboundHandler回调方法:

3 异步非阻塞

写操作:通过NioSocketChannel的write方法向连接里面写入数据时候是非阻塞的,马上会返回,即使调用写入的线程是我们的业务线程。Netty通过在ChannelPipeline中判断调用NioSocketChannel的write的调用线程是不是其对应的NioEventLoop中的线程,如果发现不是则会把写入请求封装为WriteTask投递到其对应的NioEventLoop中的队列里面,然后等其对应的NioEventLoop中的线程轮询读写事件时候,将其从队列里面取出来执行。

读操作:当从NioSocketChannel中读取数据时候,并不是需要业务线程阻塞等待,而是等NioEventLoop中的IO轮询线程发现Selector上有数据就绪时,通过事件通知方式来通知业务数据已就绪,可以来读取并处理了。

每个NioSocketChannel对应的读写事件都是在其对应的NioEventLoop管理的单线程内执行,对同一个NioSocketChannel不存在并发读写,所以无需加锁处理。

使用Netty框架进行网络通信时,当我们发起I/O请求后会马上返回,而不会阻塞我们的业务调用线程;如果想要获取请求的响应结果,也不需要业务调用线程使用阻塞的方式来等待,而是当响应结果出来的时候,使用I/O线程异步通知业务的方式,所以在整个请求 -> 响应过程中业务线程不会由于阻塞等待而不能干其他事情。

一直想整理出一份完美的面试宝典,但是时间上一直腾不开,这套一千多道面试题宝典,结合今年金三银四各种大厂面试题,以及 GitHub 上 star 数超 30K+ 的文档整理出来的,我上传以后,毫无意外的短短半个小时点赞量就达到了 13k,说实话还是有点不可思议的。

一千道互联网 Java 工程师面试题

内容涵盖:Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、Redis、mysql、Spring、SpringBoot、SpringCloud、RabbitMQ、Kafka、Linux等技术栈(485页)

初级—中级—高级三个级别的大厂面试真题

阿里云——Java 实习生/初级

List 和 Set 的区别 HashSet 是如何保证不重复的

HashMap 是线程安全的吗,为什么不是线程安全的(最好画图说明多线程环境下不安全)?

HashMap 的扩容过程

HashMap 1.7 与 1.8 的 区别,说明 1.8 做了哪些优化,如何优化的?

对象的四种引用

Java 获取反射的三种方法

Java 反射机制

Arrays.sort 和 Collections.sort 实现原理 和区别

Cloneable 接口实现原理

异常分类以及处理机制

wait 和 sleep 的区别

数组在内存中如何分配

答案展示:

美团——Java 中级

BeanFactory 和 ApplicationContext 有什么区别

Spring Bean 的生命周期

Spring IOC 如何实现

说说 Spring AOP

Spring AOP 实现原理

动态代理(cglib 与 JDK)

Spring 事务实现方式

Spring 事务底层原理

如何自定义注解实现功能

Spring MVC 运行流程

Spring MVC 启动流程

Spring 的单例实现原理

Spring 框架中用到了哪些设计模式

为什么选择 Netty

说说业务中,Netty 的使用场景

原生的 NIO 在 JDK 1.7 版本存在 epoll bug

什么是 TCP 粘包/拆包

TCP 粘包/拆包的解决办法

Netty 线程模型

说说 Netty 的零拷贝

Netty 内部执行流程

答案展示:

蚂蚁金服——Java 高级

题 1:

  1. jdk1.7 到 jdk1.8 Map 发生了什么变化(底层)?

  2. ConcurrentHashMap

  3. 并行跟并发有什么区别?

  4. jdk1.7 到 jdk1.8 java 虚拟机发生了什么变化?

  5. 如果叫你自己设计一个中间件,你会如何设计?

  6. 什么是中间件?

  7. ThreadLock 用过没有,说说它的作用?

  8. Hashcode()和 equals()和==区别?

  9. mysql 数据库中,什么情况下设置了索引但无法使用?

  10. mysql 优化会不会,mycat 分库,垂直分库,水平分库?

  11. 分布式事务解决方案?

  12. sql 语句优化会不会,说出你知道的?

  13. mysql 的存储引擎了解过没有?

  14. 红黑树原理?

题 2:

  1. 说说三种分布式锁?

  2. redis 的实现原理?

  3. redis 数据结构,使⽤场景?

  4. redis 集群有哪⼏种?

  5. codis 原理?

  6. 是否熟悉⾦融业务?记账业务?蚂蚁⾦服对这部分有要求。

好啦~展示完毕,大概估摸一下自己是青铜还是王者呢?

前段时间,在和群友聊天时,把今年他们见到的一些不同类别的面试题整理了一番,于是有了以下面试题集,也一起分享给大家~

如果你觉得这些内容对你有帮助,可以加入csdn进阶交流群,领取资料

基础篇


JVM 篇


MySQL 篇



Redis 篇




由于篇幅限制,详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

需要的小伙伴,可以一键三连,下方获取免费领取方式!
在这里插入图片描述

以上是关于Java 开发必备! I/O与Netty原理精讲(建议收藏)的主要内容,如果未能解决你的问题,请参考以下文章

Java高阶必备之Netty基础原理

电子书《Offer来了:Java面试核心知识点精讲(原理篇)》

Netty9# Netty抽象内存分配器实现原理

从网络I/O模型到Netty,先深入了解下I/O多路复用

Netty模型篇一:Netty 线程模型架构 & 工作原理 解读

一.Netty入门到超神系列-BIONIOAIO的认识