目录
Netty简介
Netty是 一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。
JDK原生NIO程序的问题
JDK原生也有一套网络应用程序API,但是存在一系列问题,主要如下:
- NIO的类库和API繁杂,使用麻烦,你需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等
- 需要具备其它的额外技能做铺垫,例如熟悉Java多线程编程,因为NIO编程涉及到Reactor模式,你必须对多线程和网路编程非常熟悉,才能编写出高质量的NIO程序
- 可靠性能力补齐,开发工作量和难度都非常大。例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等等,NIO编程的特点是功能开发相对容易,但是可靠性能力补齐工作量和难度都非常大
- JDK NIO的BUG,例如臭名昭著的epoll bug,它会导致Selector空轮询,最终导致CPU 100%。官方声称在JDK1.6版本的update18修复了该问题,但是直到JDK1.7版本该问题仍旧存在,只不过该bug发生概率降低了一些而已,它并没有被根本解决
Netty主要特点有:
- 设计优雅
- 使用方便
- 高性能
- 安全
- 社区活跃,不断更新
BIO的Acceptor线程模型
同步阻塞IO
服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。如果是基于多线程的模式来的话,就是Acceptor线程模型
AIO的Proactor模型
异步非阻塞IO
服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理
NIO的I/O多路复用模型
同步非阻塞IO
Netty的IO线程NioEventLoop由于聚合了多路复用器Selector,可以同时并发处理成百上千个客户端连接。当线程从某客户端Socket通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。
由于读写操作都是非阻塞的,这就可以充分提升IO线程的运行效率,避免由于频繁I/O阻塞导致的线程挂起,一个I/O线程可以并发处理N个客户端连接和读写操作,这从根本上解决了传统同步阻塞I/O一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。
基于buffer
传统的I/O是面向字节流或字符流的,以流式的方式顺序地从一个Stream 中读取一个或多个字节, 因此也就不能随意改变读取指针的位置。
在NIO中, 抛弃了传统的 I/O流, 而是引入了Channel和Buffer的概念. 在NIO中, 只能从Channel中读取数据到Buffer中或将数据 Buffer 中写入到 Channel。
基于buffer操作不像传统IO的顺序操作, NIO 中可以随意地读取任意位置的数据
public class FileChannelDemo1 {
public static void main(String[] args) throws Exception {
// 构造一个传统的文件输出流
FileOutputStream out = new FileOutputStream("F:\\development\\tmp\\hello.txt");
// 通过文件输出流获取到对应的FileChannel,以NIO的方式来写文件
FileChannel channel = out.getChannel();
// 将数据写入到Buffer中
ByteBuffer buffer = ByteBuffer.wrap("hello world".getBytes());
// 通过FileChannel管道将Buffer中的数据写到输出流中去,持久化到磁盘中去
channel.write(buffer);
channel.close();
out.close();
}
}
Netty线程模型
Netty主要基于主从Reactors多线程模型(如下图)做了一定的修改,其中主从Reactor多线程模型有多个Reactor:MainReactor和SubReactor:
- MainReactor负责客户端的连接请求,并将请求转交给SubReactor
- SubReactor负责相应通道的IO读写请求
- 非IO请求(具体逻辑处理)的任务则会直接写入队列,等待worker threads进行处理
这里引用Doug Lee大神的Reactor介绍:Scalable IO in Java里面关于主从Reactor多线程模型的图
事件驱动模型
通常,我们设计一个事件处理模型的程序有两种思路
- 轮询方式
线程不断轮询访问相关事件发生源有没有发生事件,有发生事件就调用事件处理逻辑。
- 事件驱动方式
发生事件,主线程把事件放入事件队列,在另外线程不断循环消费事件列表中的事件,调用事件对应的处理逻辑处理事件。事件驱动方式也被称为消息通知方式,其实是设计模式中观察者模式的思路。
参考文章: