Netty介绍
原生NIO存在的问题
-
NIO的类库和API复杂,使用起来比较麻烦,需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等API的使用。
-
需要具备一些额外的技能:Java多线程、Reactor模式、必须对多线程和网络编程非常熟悉、才能写出高质量的NIO程序。
-
开发工作量和难度非常大:例如客户端面临重连、网络闪断、半包读写、失败缓存、网络阻塞和异常流的处理等等。
-
JDK NIO的BUG:Epoll Bug,它会导致 Selector 空轮询,最终导致 CPU 100%。直到JDK1.7版本该问题依然存在,没有被根本解决。
在NIO中通过Selector的轮询,查看当前通道是否有IO事件,根据JDK NIO API的描述,Selector的select()方法会一直阻塞,直到IO事件完成或超时,但是在Linux平台上有时会出问题,在某些场景下select()方法会直接返回,即使没有超时并且也没有IO事件到达,这就是著名的epoll bug,这是一个比较严重的bug,他会导致线程陷入死循环,会让CPU飙升到100%,极大地影响到系统的可靠性,到目前为止,JDK都没有完全解决这个问题。
概述
Netty是JBoss提供的一个Java开源框架。提供异步的,基于事件驱动的网络应用程序框架,用以快速地开发高性能,高可靠性的网络IO程序。Netty是一个基于NIO的网络编程框架,使用Netty可以帮你快速、简单的开发一个网络应用,相当于简化和流程化了NIO的开发过程。作为当前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,知名的Elasticsearch、Dubbo框架内部都采用了Netty。
Netty的强大之处:零拷贝、可拓展的事件模型;支持TCP、UDP、HTTP、WebSocket等协议;提供安全传输、压缩、大文件传输、编解码支持等等。
具备如下优点:
-
设计优雅,提供阻塞和非阻塞的Socket;
-
提供灵活可拓展的事件模型;
-
提供高度可定制的线程模型;
-
具备更高的性能和更大的吞吐量,使用零拷贝技术最小化不必要的内存复制,减少资源的消耗;
-
提供安全传输特性;
-
支持多种主流协议;预置多种编解码功能,支持用户开发私有协议。
线程模型
概述
不同的线程模式,对程序的性能有很大影响,在学习Netty线程模式之前,首先了解下各个线程模式,最后看看Netty线程模型有哪些优越性。
目前存在的线程模型有:
-
传统的阻塞I/O服务模型
-
Reactor模型:根据Reactor的数量和处理资源池线程的数量不同,有三种典型的实现
-
- 单Reactor 单线程
- 单Reactor 多线程
-
- 主从Reator 多线程
传统阻塞的 I/O 服务模型
采用阻塞 I/O 模式获取输入的数据,每个连接都需要独立的线程完成数据的输入,业务处理和数据返回的工作。
这种模式存在问题:
- 当并发数很大的时候,会创建大量的线程,占用很大的系统资源。
- 连接创建后,如果当前线程暂时没有数据可读,该线程会阻塞在read操作,造成系统资源的浪费。
Reactor 模型
Reactor模式,通过一个或者多个输入同时传递给服务器处理器的模式,服务器端程序处理传入的多个请求,并将它们同步分派到相应的处理线程,因此Reactor模式也叫Dispatch模式。
Reactor模式使用IO复用监听事件,收到事件后,分发给某个线程(进程),这点就是网络服务器高并发处理的关键。
下面我们分别看下Reactor的三种经典模式:
单 Reactor 单线程
-
Selector 可以实现应用程序通过一个阻塞对象监听多路连接请求。
-
Reactor 对象通过Selector监控客户端连接事件,收到事件之后通过 Dispatch进行分发。
-
建立连接,请求事件,则由Acceptor处理连接请求,然后创建一个Handler对象处理完成后的后续业务处理。
-
Handler会完成Read->业务处理->Send 这样一个完成业务流程。
优点:
模型简单,没有多线程,进程通信,竞争等问题,全部都在一个线程中完成
缺点:
- 存在性能问题:只有一个线程,无法完全发挥多核CPU的性能,Handler在处理某个连接上的业务时,整个进程无法处理其他连接事件,很容易导致性能瓶颈。
- 存在可靠性问题:线程意外终止或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障。
单 Reactor 多线程
-
Reactor 对象通过 Selector 监控客户端请求事件,收到事件后,通过 Dispatch 进行分发。
-
如果建立连接请求,则右 Acceptor 通过 accept 处理连接请求。
-
如果不是连接请求,则由 Reactor 分发调用连接对应得Handler 处理。
-
Handler 只负责响应事件,不做具体的业务处理(只做 read 和 send 操作),通过 read 读取数据后,会分发给后面的 Worker 线程池中的某个线程处理业务。
-
Worker 线程池会分配独立线程完成真正的业务,并将结果返回给 Handler。
-
Handler 收到响应后,通过 send 将结果返回给客户端。
优点:
可以充分利用多核CPU的处理能力
缺点:
多线程数据共享和访问比较复杂,Reactor 处理所有的事件的监听和响应,再单线程运行,再高并发场景容易出现性能瓶颈。
主从 Reactor 多线程
-
Reactor 主线程 MainReactor 对象通过 select() 监听客户端连接事件,收到事件后,通过 Acceptor 处理客户端连接事件。
-
当 Acceptor 处理完客户端连接事件之后(与客户端建立好 Socket 连接),MainReactor 将连接分配给 SubReactor。(即:MainReactor 只负责监听客户端连接请求,和客户端建立连接之后将连接交由SubReactor 监听后面的 IO事件。)
-
SubReactor 将连接加入到自己的连接队列进行监听,并创建 Handler 对各种事件进行处理。
-
当连接上有新事件发生的时候,SubReactor 就会调用对应的 Handler 处理。
-
Handler 通过 Read 从连接上读取请求数据,将请求数据分发给 Worker 线程池进行业务处理。
-
Worker 线程池会分配独立线程来完成真正的业务处理,并将结果返回给 Handler。Handler 通过 Send 向客户端发送响应数据。
-
一个 MainReactor 可以对应多个 SubReactor,即一个 MainReactor 线程可以对应多个 SubReactor 线程。
优点:
-
MainReactor 线程与 SubReactor 线程的数据交互简单职责明确,MainReactor 线程只需要接收新连接,SubReactor 线程完成后续的业务处理。
-
MainReactor 线程与 SubReactor 线程的数据交互简单,MainReactor 线程只需要把新连接传给 SubReactor 线程,SubReactor 线程无需返回数据。
-
多个 SubReactor 线程能够应对更高的并发请求
缺点:
- 这种模式的缺点就是编程复杂度较高。但是由于其优点明显,再许多项目中被广泛应用,包括 Nginx,Memcached、Netty 等。
- 这种模式也叫服务器的1+M+N 线程模式,即使用该模式开发的服务器包含一个(或多个, 1只表示相对较少)连接建立线程,+M 个IO线程+N 个业务处理线程。这是业界成熟的服务器程序设计模式。