Netty
# NIO、BIO、AIO的区别
# 题⽬描述
# NIO、BIO、AIO的区别
# 1. ⾯试题分析
根据题⽬要求我们可以知道:
IO模型分类
- BIO、NIO、AIO
- IO与NIO区别
- 同步与异步的区别
- 阻塞和⾮阻塞
分析需要全⾯并且有深度
容易被忽略的坑
分析⽚⾯
没有深⼊
# 2. IO模型主要分类
- 同步(synchronous) IO和异步(asynchronous) IO
- 阻塞(blocking) IO和⾮阻塞(non-blocking)IO
- 同步阻塞(blocking-IO)简称BIO
- 同步⾮阻塞(non-blocking-IO)简称NIO
- 异步⾮阻塞(synchronous-non-blocking-IO)简称AIO
# 1.BIO (同步阻塞I/O模式)
数据的读取写⼊必须阻塞在⼀个线程内等待其完成。
这⾥使⽤那个经典的烧开⽔例⼦,这⾥假设⼀个烧开⽔的场景,有⼀排⽔壶在烧开⽔,BIO的⼯作模式就是, 叫⼀个线程停留在⼀个⽔壶那,直到这个⽔壶烧开,才去处理下⼀个⽔壶。但是实际上线程在等待⽔壶烧开的时间段什么都没有做。
# 2.NIO(同步⾮阻塞)
同时⽀持阻塞与⾮阻塞模式,但这⾥我们以其同步⾮阻塞I/O模式来说明,那么什么叫做同步⾮阻塞?如果还拿烧开⽔来说,NIO的做法是叫⼀个线程不断的轮询每个⽔壶的状态,看看是否有⽔壶的状态发⽣了改变,从⽽进⾏下⼀步的操作。
# 3.AIO (异步⾮阻塞I/O模型)
异步⾮阻塞与同步⾮阻塞的区别在哪⾥?异步⾮阻塞⽆需⼀个线程去轮询所有IO操作的状态改变,在相应的状态改变后,系统会通知对应的线程来处理。对应到烧开⽔中就是,为每个⽔壶上⾯装了⼀个开关,⽔烧开之后,⽔壶会⾃动通知我⽔烧开了。
# 4.IO与NIO区别
# 5.同步与异步的区别
**同步:**发送⼀个请求,等待返回,再发送下⼀个请求,同步可以避免出现死锁,脏读的发⽣。
**异步:**发送⼀个请求,不等待返回,随时可以再发送下⼀个请求,可以提⾼效率,保证并发。
# 6.阻塞和⾮阻塞
**阻塞:**传统的IO流都是阻塞式的。也就是说,当⼀个线程调⽤read()或者write()⽅法时,该线程将被阻塞,直到有⼀些数据读读取或者被写⼊,在此期间,该线程不能执⾏其他任何任务。在完成⽹络通信进⾏IO操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供⼀个独⽴的线程进⾏处理,当服务器端需要处理⼤量的客户端时,性能急剧下降。
**⾮阻塞:**Java NIO是⾮阻塞式的。当线程从某通道进⾏读写数据时,若没有数据可⽤时,该线程会去执⾏其他任务。线程通常将⾮阻塞IO的空闲时间⽤于在其他通道上执⾏IO操作,所以单独的线程可以管理多个输⼊和输出通道。因此NIO可以让服务器端使⽤⼀个或有限⼏个线程来同时处理连接到服务器端的所有客户端。
# 7.BIO、NIO、AIO适⽤场景
- BIO⽅式适⽤于连接数⽬⽐较⼩且固定的架构,这种⽅式对服务器资源要求⽐较⾼,并发局限于应⽤中,JDK1.4以前的唯⼀选择。
- NIO⽅式适⽤于连接数⽬多且连接⽐较短(轻操作)的架构,⽐如聊天服务器,并发局限于应⽤中,编程⽐较复杂。
- AIO⽅式使⽤于连接数⽬多且连接⽐较⻓(重操作)的架构,⽐如相册服务器,充分调⽤OS参与并发操作,编程⽐较复杂,JDK7开始⽀持。
# 3. NIO的3个核⼼概念
NIO重点是把**Channel(通道),Buffer(缓冲区),Selector(选择器)**三个类之间的关系弄清楚。
# 1.缓冲区Buffer
Buffer是⼀个对象。它包含⼀些要写⼊或者读出的数据。在⾯向流的I/O中,可以将数据写⼊或者将数据直接读到Stream对象中。
在NIO中,所有的数据都是⽤缓冲区处理。这也就本⽂上⾯谈到的IO是⾯向流的,NIO是⾯向缓冲区的。
缓冲区实质是⼀个数组,通常它是⼀个字节数组(ByteBuffer),也可以使⽤其他类的数组。但是⼀个缓冲区不仅仅是⼀个数组,缓冲区提供了对数据的结构化访问以及维护读写位置(limit)等信息。
最常⽤的缓冲区是ByteBuffer,⼀个ByteBuffer提供了⼀组功能于操作byte数组。除了ByteBuffer,还有其他的⼀些缓冲区,事实上,每⼀种Java基本类型(除了Boolean)都对应⼀种缓冲区,具体如下:
- ByteBuffer:字节缓冲区
- CharBuffer:字符缓冲区
- ShortBuffer:短整型缓冲区
- IntBuffer:整型缓冲区
- LongBuffer:⻓整型缓冲区
- FloatBuffer:浮点型缓冲区
- DoubleBuffer:双精度浮点型缓冲区
# 2.通道Channel
Channel是⼀个通道,可以通过它读取和写⼊数据,他就像⾃来⽔管⼀样,⽹络数据通过Channel读取和写⼊。
通道和流不同之处在于通道是双向的,流只是在⼀个⽅向移动,⽽且通道可以⽤于读,写或者同时⽤于读写。
因为Channel是全双⼯的,所以它⽐流更好地映射底层操作系统的API,特别是在UNIX⽹络编程中,底层操作系统的通道都是全双⼯的,同时⽀持读和写。
Channel有四种实现:
- FileChannel:是从⽂件中读取数据。
- DatagramChannel:从UDP⽹络中读取或者写⼊数据。
- SocketChannel:从TCP⽹络中读取或者写⼊数据。
- ServerSocketChannel:允许你监听来⾃TCP的连接,就像服务器⼀样。每⼀个连接都会有⼀个SocketChannel产⽣。
# 3.多路复⽤器Selector
Selector选择器可以监听多个Channel通道感兴趣的事情(read、write、accept(服务端接收)、
connect,实现⼀个线程管理多个Channel,节省线程切换上下⽂的资源消耗。Selector只能管理⾮阻塞的通道,FileChannel是阻塞的,⽆法管理。
关键对象
- Selector:选择器对象,通道注册、通道监听对象和Selector相关。
- SelectorKey:通道监听关键字,通过它来监听通道状态。
监听注册
监听注册在Selector
socketChannel.register(selector, SelectionKey.OP_READ);
监听的事件有
- OP_ACCEPT: 接收就绪,serviceSocketChannel使⽤的
- OP_READ: 读取就绪,socketChannel使⽤
- OP_WRITE: 写⼊就绪,socketChannel使⽤
- OP_CONNECT: 连接就绪,socketChannel使⽤
# 4. 扩展内容
NIO、BIO、AIO的应⽤和框架选型
# Netty的实现原理、特点与优势、以及适用场景
# 题目描述
Netty的实现原理、特点与优势、以及适用场景
# 面试题分析
根据题目要求我们可以知道:
- 介绍
- Netty的特点
- 为什么选择Netty
- Netty框架的优势
- Netty的架构设计
- Netty的高性能设计
- Netty的核心组件
分析需要全面并且有深度
容易被忽略的坑
分析片面
没有深入
# 1.介绍
Netty是由JBOSS提供的一个java开源框架。
Netty是一个高性能、异步事件驱动的NIO框架,它提供了对TCP、UDP和文件传输的支持。作为当前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通
信行业等获得了广泛的应用,一些业界著名的开源组件也基于Netty的NIO框架构建(文章尾有详细介绍)。
# 2.Netty的特点
# 高并发
Netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通信框架,对比于BIO(Blocking I/O,阻塞IO),他的并发性能得到了很大提高 。
# 传输快
Netty的传输快其实也是依赖了NIO的一个特性——零拷贝。
# 封装好
Netty封装了NIO操作的很多细节,提供易于使用的API。
# 3.什么选择Netty
JDK 原生也有一套网络应用程序 API,但是存在一系列问题,主要如下:
1)NIO 的类库和 API 繁杂,使用麻烦:你需要熟练掌握SelectorServerSocketChannel、SocketChannel、ByteBuffer 等。
2)需要具备其他的额外技能做铺垫:例如熟悉 Java 多线程编程,因为 NIO 编程涉及到 Reactor 模式,你必须对多线程和网路编程非常熟悉,才能编写出高质量的 NIO 程序。
3)可靠性能力补齐,开发工作量和难度都非常大:例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等等。NIO 编程的特点是功能开发相对容易,但是可靠性能力补齐工作量和难度都非常大。
4)JDK
NIO 的 Bug:例如臭名昭著的 Epoll Bug,它会导致 Selector空轮询,最终导致 CPU 100%。官方声称在 JDK1.6 版本的 update 18 修复了该问题,但是直到 JDK 1.7 版本该问题仍旧存在,只不过该 Bug发生概率降低了一些而已,它并没有被根本解决。
# 4.Netty框架的优势
- API使用简单,开发门槛低;
- 功能强大,预置了多种编解码功能,支持多种主流协议;
- 定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展;
- 性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优;
- 成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼;
- 社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时,更多的新功能会加入;
- 经历了大规模的商业应用考验,质量得到验证。在互联网、大数据、网络游戏、企业应用、电信软件等众多行业得到成功商用,证明了它已经完全能够满足不同行业的商业应用了。
# 5.Netty的架构设计
# 总体结构
Netty 采用了比较典型的三层网络架构进行设计,逻辑架构图如下所示:
1)传输服务:支持 BIO 和 NIO;
2)容器集成:支持 OSGI、JBossMC、Spring、Guice 容器;
3)协议支持:HTTP、Protobuf、二进制、文本、WebSocket等一系列常见协议都支持。还支持通过实行编码解码逻辑来实现自定义协议;
4)Core 核心:可扩展事件模型、通用通信 API、支持零拷贝的 ByteBuf 缓冲对象。
# 6.Netty的高性能设计
# 1.高性能的三大要素
传输:用什么样的通道将数据发送给对方,BIO、NIO或者AIO,IO模型在很大程度上决定了框架的性能。
协议:采用什么样的通信协议,HTTP或者内部私有协议。协议的选择不同,性能模型也不同。相比于公有协议,内部私有协议的性能通常可以被设计的更优。
线程:数据报如何读取?读取之后的编解码在哪个线程进行,编解码后的消息如何派发,Reactor线程模型的不同,对性能的影响也非常大。
# 2.IO模型
Netty的I/O模型基于非阻塞I/O实现,底层依赖的是JDK NIO框架的Selector。
# 3.Reactor线程模型
关于Java NIO 构造Reator模式,Doug lea在《Scalable IO in Java》中给了很好的阐述,这里截取PPT对Reator模式的实现进行说明。
1)Reactor单线程模型:Reactor单线程模型,指的是所有的I/O操作都在同一个NIO线程上面完成。对于一些小容量应用场景,可以使用单线程模型。
2)Reactor多线程模型:Rector多线程模型与单线程模型最大的区别就是有一组NIO线程处理I/O操作。主要用于高并发、大业务量场景。
3)主从Reactor多线程模型:主从Reactor线程模型的特点是服务端用于接收客户端连接的不再是个1个单独的NIO线程,而是一个独立的NIO线程池。利用主从NIO线程模型,可以解决1
个服务端监听线程无法有效处理所有客户端连接的性能不足问题。
# 4.Netty的线程模型
说完了Reactor的三种模型,那么Netty是哪一种呢?其实Netty的线程模型是Reactor模型的变种,那就是去掉线程池的第三种形式的变种,这也是Netty NIO的默认模式。
# 7.Netty的核心组件
Netty应用中必不可少的组件:
- Bootstrap or ServerBootstrap
- EventLoop
- EventLoopGroup
- ChannelPipeline
- Channel
- Future or ChannelFuture
- ChannelInitializer
- ChannelHandler
# 1.Bootstrap
一个Netty应用通常由一个Bootstrap开始,它主要作用是配置整个Netty程序,串联起各个组件。
Handler,为了支持各种协议和处理数据的方式,便诞生了Handler组件。Handler主要用来处理各种事件,这里的事件很广泛,比如可以是连接、数据接收、异常、数据转换等。
# 2.ChannelInboundHandler
一个最常用的Handler。这个Handler的作用就是处理接收到数据时的事件,也就是说,我们的业务逻辑一般就是写在这个Handler里面的,ChannelInboundHandler就是用来处理我们
的核心业务逻辑。
# 3.ChannelInitializer
当一个链接建立时,我们需要知道怎么来接收或者发送数据,当然,我们有各种各样的Handler实现来处理它,那么ChannelInitializer便是用来配置这些Handler,它会提供一个ChannelPipeline,并把Handler加入到ChannelPipeline。
# 4.ChannelPipeline
一个Netty应用基于ChannelPipeline机制,这种机制需要依赖于EventLoop和EventLoopGroup,因为它们三个都和事件或者事件处理相关。
EventLoops的目的是为Channel处理IO操作,一个EventLoop 可以为多个Channel服务。
EventLoopGroup会包含多个EventLoop。
# 5.Channel
代表了一个Socket链接,或者其它和IO操作相关的组件,它和EventLoop一起用来参与IO处理。
# 6.Future
在Netty中所有的IO操作都是异步的,因此,你不能立刻得知消息是否被正确处理,但是我们可以过一会等它执行完成或者直接注册一个监听,具体的实现就是通过Future和ChannelFutures,他们可以注册一个监听,当操作执行成功或失败时监听会自动触发。
总之,所有的操作都会返回一个ChannelFuture。
# 8. 扩展内容
Netty的应用场景?
高并发编程系列:NIO、BIO、AIO的区别,及NIO的
应用?