Netty的介绍
- Netty是一个异步的、基于事件驱动的网络应用框架,用以快速开发高性能、高可靠性的网络IO程序
- Netty 主要针对在TCP协议下,面向Clients端的高并发应用,或者Peer-to-Peer场景下的大量数据持续传输的 应用
- Netty本质是一个NIO框架,适用于服务器通讯相关的多种应用场景
I/O模型
- I/O模型简单的理解:就是用什么样的通道进行数据的发送和接收,很大程度上决定了程序通信的性能
- Java共支持3种网络编程模型I/O模式:BIO、NIO、AIO
- Java BIO: 同步并阻塞,服务器实现模式为一个连接一个线程,即看客户端有链接请求服务器端就需要启动一个线程进行处理,如果这个链接不做任何事情会造成不必要的线程开销
- Java NIO:同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理
- Java AIO(NIO.2):异步非阻塞,AIO引入异步通道的概念,采用了Proactor模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用
BIO、NIO、AIO适用场景分析
- BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,Jdk1.4以前的唯一选择,但是程序简单易理解
- NIO方式适用于连接数多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕系统,服务器间的通信等。编程比较复杂,jdk1.4开始支撑
- AIO方式适用于连接数多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发的操作,编程比较复杂,JDK7开始支持
BIO问题分析
- 每个都需要创建独立的线程,与对应的客户端进行数据Read,业务处理,数据Write
- 当并发数较大时,需要创建大量线程来处理连接,系统资源占用较大
- 连接建立后,如果当前线程,暂时没有数据刻度,则线程就阻塞在Read操作上,造成线程资源浪费
NIO基本介绍
- Java NIO全程 java non-blocking IO,是指JDK提供新的API。从jdk1.4爱是,java提供了一系列改进的输入/输出的新特性,被统称为NIO
- NIO相关类都放在java.nio包集子包下,并且对原java.io包重的很多类进行改写
- NIO有三大核心部分:Channel(通道)、Buffer(缓冲区)、Selector(选择器)
- NIO是面向缓冲区,或者面向块编程的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络
- Java NIO的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程堵塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情,非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情
- 通俗理解,NIO是可以做到用一个线程来处理多个操作的,假设有10000个请求过来,根据实际情况,可以分配到50或者100个线程来处理。
- HTTP2.0使用了多路复用的技术,世道同一个链接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级
NIO与BIO的比较
- BIO以流的方式处理数据,而NIO以块的方式处理数据,块I/O的效率比流I/O高很多
- BIO是阻塞的,NIO是非阻塞的
- BIO基于字节流和字符流进行操作,而NIO基于Channel(通道)和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件 (比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道
NIO的Selector、Channel和Buffer的关系
- 每个Channel都会对应一个Buffer
- Selector对应一个线程,一个线程对应多个Channel
- Chennel会注册到Selector
- 程序切换到哪个Channel是由事件决定的
- Selector会根据不同的事件,在各个通道上切换
- Buffer就是一个内存块,底层是一个数组
- 数据的读取写入是通过Buffer,和BIO是不同的,BIO中是输入流/输出流,BIO中的Buffer可读写、经过flip方法切换。Channel是双向的,可以返回底层操作系统的情况,比如Linux,底层操作系统通道就是双向的
Buffer
Buffer类定义了所有缓冲区都具有的四个属性来提供关于其包含的数据元素的信息
private int mark = -1; //标记 private int position = 0; //位置,下一个要被读或写的原书的索引,每次读写缓冲区数据时都会改变值,为下次读写做准备 private int limit; //表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作。且基线是可以修改的 private int capacity; // 容量,即可以容纳的最大数据量,在缓冲区创建时被设定并且不能改变
Channel
- BIO中的stream是单向的,例如FileInputStream对象只能进行读取数据的操作,而NIO中的通道(channel)是双向的。可读、写
- Channel在NIO中是一个接口
- 常用的Channel类有:FileChannel、DatagramChannel、ServerSocktChannel和SocketChannel
- FileChannel用于文件的数据读写,DatagramChannel用于UDP的数据读写,ServerSocketChannel和SocketChannel用于TCP的数据读写
关于Buffer和Channel的注意事项和细节
- ByteBuffer支持类型化的put和get,put放入的是什么数据类型,gat就应该使用相应的数据类型来取出,否则可能有BufferUnderflowException异常
- 可以将一个普通Buffer转成只读Buffer
- NIO还提供了MappedByteBuffer,可以让文件直接在内存(堆外的内存)中进行修改,而如何同步到文件由NIO来完成
- NIO还支持通过多个Buffer(即Buffer数组)完成读写操作。即Scattering和Gethering
Selector(选择器)
- java的NIO,用非阻塞的IO方式,可以用一个线程,处理多个的客户端连接,就会使用到Selector(选择器)
- Selector能够检测多个注册的通道上是否有事件发生(注意:多个Channel以事件的方式可以注册到同一个Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理,这样就可以只用一个单线程区去管理多个通道,也就是管理多个来连接和请求。
- 只有在连接真正有读写事件发生时,才会进行读写,就大大地减少了系统的开销,而且不必为每个连接都创建一个线程,不用去维护多个线程
NIO网络通信流程
原生NIO存在的问题
- NIO的类库和API繁杂,使用麻烦;需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等。
- 需要具备其他的额外技能,要熟练Java多线程编程,因为NIO编程涉及到Reactor模式,你必须对多线程和网络编程非常熟悉,才能编写出高质量的NIO程序
- 开发工作量和难度都非常大,例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常流的处理等等
- JDK NIO的bug,例如Epoll Bug,它会导致Selector空轮询,最终导致CPU 100%,直到JDK 1.7版本该问题仍旧存在,没有被根本解决
Netty官网说明
- Netty是由JBOSS提供的一个Java开源框架。Netty提供异步的、基于事件驱动的网络应用程序架构,用以快速开发高性能、高可靠性的网络IO程序。
- Netty可以帮助你快速、简单的开发出一个网络应用,相当于简化和流程化了NIO的开发过程
- Netty是目前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,知名的Elasticsearch、Dubbo框架内部都采用了Netty
Netty的优点
Netty对JDK自带的NIO的API进行了封装,解决了上述的问题
- 设计优雅,适用于各种传输类型的统一API阻塞和非阻塞Socket; 基于灵活且可扩展的事件模型,可以清晰的分离关注点;高度可指定的线程模型-单线程,一个或多个线程池
- 使用方便:详细记录的Javadoc,用户指南和示例:没有其他依赖项,JDK 5(Netty 3.x)或6 (Netty 4.x) 就足够了。
- 高性能、吞吐量更高:延迟更低;减少资源消耗;最小化不惜要的内存复制。
- 安全:完整的SSL/TLS和StartTLS支持
- 社区活跃、不断更新:社区活跃,版本迭代周期短,发现的Bug可以被及时修复,同时更多的新功能会被加入。
单Reactor单线程
方案优缺点分析
- 优点:模型简单,没有多线程、进程通信、竞争的问题,全部都在一个进程中完成。
- 缺点:性能问题,只有一个线程,无法完全发挥多核CPU的性能。Handler在处理某个连接上的业务时,整个进程无法处理其他连接事件,很容易导致性能瓶颈
- 缺点:可靠性问题,县城意外终止,或者进入死循环,会导致整个通信模块不可用,不能接收和处理外部消息,造成节点故障
- 使用场景:客户端的数量有限,业务处理非常快速,比如Redis在业务处理的时间复杂度O(1)的情况
单Reactor多线程
- Reactor对象通过select监控客户端请求事件,收到事件后,通过dispatch进行分发
- 如果建立连接请求后,则由Accpetor通过accpet处理连接请求,然后创建一个Handler对象处理完成连接后的各种事件
- 如果不是连接请求,则由reactor分发调用连接对应的handler来处理
- handler只负责响应事件,不做具体的业务处理,通过read读取数据就,会分批发给worker线程池的某个线程处理业务
- worker线程池会分配独立线程完成真正的业务,并将结果返回handler
- handler收到响应后,通过send将结果返回给client
方案优缺点分析
- 优点:可以充分利用多核cpu的处理能力
- 缺点:多线程数据共享和访问比较复杂,reactor处理所有的事件的监听和响应,在单线程运行。在高并发场景,容易出现性能瓶颈。
目前文章正在整理中,目录和文字比较混乱。。。