文章目录
前言
最近在研究Flink源码时,才第一次听到Netty这个框架,因为Flink的内部数据通信和传输是基于Netty来实现的。后来查了下,Spark内部的数据传输也是基于Netty。所以不得不对Netty做番认识。主要是方便以后定位问题。
1. Netty简单介绍
Netty是一个NIO客户端-服务器框架,它支持快速、简单地开发网络应用程序,如协议服务器和客户机。它大大简化了网络编程,如TCP和UDP套接字服务器。
“快速和简单”并不意味着生成的应用程序将受到可维护性或性能问题的影响。Netty经过精心设计,并积累了许多协议(如ftp、smtp、http)的实施经验,以及各种二进制和基于文本的遗留协议。因此,Netty成功地找到了一种方法,可以在不妥协的情况下实现轻松的开发、性能、稳定性和灵活性。
附上一张Netty官网的架构图:
主要特点
设计:
- 各种传输类型的统一API-阻塞和非阻塞套接字
- 基于一个灵活的可扩展事件模型,该模型允许清晰的关注点分离
- 高度可定制的线程模型-单线程、一个或多个线程池(如SEDA)
- 真正的无连接数据报套接字支持(从3.1开始)
易用:
- 很好的javadoc,官网demo
- 没有其他的依赖
性能:
- 高吞吐,低延迟
- 更少的资源消耗
- 减少非必需的内存拷贝
安全性:
- 完整的SSL/TLS和StartTLS支持
2. Reactor模型
Netty是典型的Reactor模型结构,所以有必要先了解下Reactor模型。Reactor模型就是将消息放到了一个队列中,通过异步线程池对其进行消费。
Reactor中的组件:
- Reactor: Reactor是IO事件的派发者。
- Acceptor: Acceptor接受client连接,建立对应client的Handler,并向Reactor注册此Handler。
- Handler: 和一个client通讯的实体,按这样的过程实现业务的处理。一般在基本的Handler基础上还会有更进一步的层次划分,用来抽象诸如decode,process和encoder这些过程。比如对Web Server而言,decode通常是HTTP请求的解析,process的过程会进一步涉及到Listener和Servlet的调用。业务逻辑的处理在Reactor模式里被分散的IO事件所打破,所以Handler需要有适当的机制在所需的信息还不全(读到一半)的时候保存上下文,并在下一次IO事件到来的时候(另一半可读了)能继续中断的处理。为了简化设计,Handler通常被设计成状态机,按GoF的state pattern来实现。
2.1. Reactor单线程模型
这个模型和上面的NIO流程很类似,只是将消息相关处理独立到了Handler中去了!
虽然上面说到NIO一个线程就可以支持所有的IO处理。但是瓶颈也是显而易见的!我们看一个客户端的情况,如果这个客户端多次进行请求,如果在Handler中的处理速度较慢,那么后续的客户端请求都会被积压,导致响应变慢!所以引入了Reactor多线程模型!
2.2. Reactor多线程模型
Reactor多线程模型就是将Handler中的IO操作和非IO操作分开,操作IO的线程称为IO线程,非IO操作的线程称为工作线程!这样的话,客户端的请求会直接被丢到线程池中,客户端发送请求就不会堵塞!
但是当用户进一步增加的时候,Reactor会出现瓶颈!因为Reactor既要处理IO操作请求,又要响应连接请求!为了分担Reactor的负担,所以引入了主从Reactor模型!
2.3. 主从Reactor模型
主Reactor用于响应连接请求,从Reactor用于处理IO操作请求!
3. NioEventLoopGroup 与 Reactor 线程模型的对应
Netty的线程模型并发固定不变,通过在启动辅助类中创建不同的EventLoopGroup实例并进行适当的参数配置,就可以支持上述三种Reactor线程模型。
3.1. Netty实现单线程模型
服务端代码如下:
/**
* Netty单线程模型服务端代码示例
* @param port
*/
public void bind(int port) {
EventLoopGroup reactorGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(reactorGroup, reactorGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("http-codec", new HttpServerCodec());
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
//后面代码省略
}
});
Channel ch = b.bind