Netty 的设计主要基于主从 Reactor 多线程模式,并做了一定的改进。
简单版 Netty 模型
- BossGroup 线程维护 Selector、ServerSocketChannel 注册到这个 Selector 上,只管连接请求并建立请求事件(主 Reactor 角色)。
- 当接收到来自客户端的连接建立请求事件的时候,通过 ServerScoketChannel.accept() 方法获得对应的 SocketChannel,并封装成 NIOSocketChannel 注册到 WorkerGroup 线程中的 Selector,每个 Selector 运行一个线程(从 Reactor)。
- 当 WorkerGroup 线程中的 Selector 监听到自己感兴趣的 IO 事件后,就调用 Handler 进行业务处理。
进阶版的 Netty 线程模型
- 有两组线程池:BossGroup 和 WorkerGroup,BossGroup 中的线程专门负责和客户端建立连接,WorkerGroup 中的线程专门负责处理连接上的读写。
- BossGroup 和 WorkerGroup 含有多个不断循环的执行事件处理的线程,每个线程都包含一个 Selector,用于监听注册在其上的 Channel。
- 每个BossGroup 中的线程循环执行以下三个步骤:
-
- 轮询注册在其上的 ServerSocketChannel 的 accept 事件(OP_ACCEPT事件)
- 处理 accept 事件,与客户端建立连接,生成一个 NIOSocketChannel,并将其注册到 WorkGroup 中某个线程上的 Selector 上
-
- 再去以此循环处理任务队列的下一个事件
- 每个 WorkerGroup 中的线程循环执行以下三个步骤:
-
- 轮询注册在其上的 NIOSocketChannel 的 read/write 事件 (OP_READ/OP_WRITE 事件)。
- 在对应的 NIOSocketChannel 上处理 read/write 事件。
-
- 再去以此循环处理任务队列中的下一个事件。
详细版的 Netty 模型
Netty 抽象出两组线程池:BossGroup 和 WorkerGroup,也可以叫做 BossNIOEventLoopGroup 和 WorkerNIOEventLoopGroup。每个线程池中都有 NIOEventLoop 线程。BossGroup 中的线程专门负责和客户端建立连接,WorkerGroup 中的线程专门负责处理连接上的读写。BossGroup 和 WorkerGroup 的类型都是NIOEventLoopGroup。
NIOEventLoopGroup 相当于一个事件循环组,这个组中含有多个事件循环,每个事件循环就是一个 NIOEventLoop,NIOEventLoop 表示一个不断循环的执行事件处理的线程,每个NIOEventLoop 都包含一个 Selector,用于监听注册在其上的 Socket 网络连接(Channel)。NIOEventLoopGroup 可以含有多个线程,也就是可以含有多个 NIOEventLoop
- 每个 BossNIOEventLoop 中循环执行以下三个步骤:
-
- select():轮询注册在其上的 ServerSocketChannel 的 Accept 事件(事件状态为:OP_ACCEPT)。
- processSelectedKeys():处理 Accept 事件,与客户端建立连接,生成一个 NIOSocketChannel,并将其注册到某个 WorkerNIOEventLoop 上的 Selector上。
-
- runAllTasks:再去以此循环处理任务队列中的其他任务。
- 每个 WorkerNIOEventLoop 中循环执行以下三个步骤:
-
- select():轮询注册在其上的 NIOSocketChannel 的 Read / Write 事件(事件状态为:OP_READ或OP_WRITE)。
- processSelectedKeys:在对应的 NIOSocketChannel 上处理 Read / Write 事件。
-
- runAllTasks:再去以此循环处理任务队列中的其他任务。
- 在以上两个 processSelectedKeys 步骤中,会使用 Pipeline(管道),Pipeline 中引用了 Channel,即通过 Pipeline 可以获取到对应的 Channel,Pipeline 中维护了很多的处理器(拦截处理器、过滤处理器、自定义处理器等)。
Netty-异步模型
当一个异步过程的调用发出后,调用者不能立即得到结果。实际处理这个调用的组件在完成后,通过状态,通知和回调来通知调用者。Netty中的 I/O 操作是异步的,包括 Bind、Write、Connect 等操作会简单的返回一个 ChannelFuture。调用者并不能立即获得结果,而是通过 Future_Listener 机制,用户可以方便的主动获取或者通过通知机制获得 I/O 操作结果。Netty 的异步模型是建立在 Future 和 Callback 之上的(Callback 就是回调)。
Future 的核心思想是:假设一个方法fun(),计算过程中可能非常耗时,这个时候等待 fun() 返回结果显然不合适。那么可以在调用 fun() 的时候,立马返回一个 Future。后续可以通过 Future 去监控方法 fun() 的处理过程(即:Future-Listener 机制)。
Future 和 Future-Listener
Future
表示异步的执行结果,可以通过它提供的方法来检测执行是否完成,ChannelFuture 是它的一个子接口,可以添加监听器,当监听的事件发生时,通知到监听器。
当 Future 对象刚创建时,处于非完成状态,调用者可以通过返回的 ChannelFuture 来获取操作执行的状态,注册监听函数来执行完成后的操作。
常用的方法有:
- sync():阻塞等待程序结果返回
- isDone():判断当前操作是否完成
- isSuccess():判断已完成的当前操作是否成功
- getCause():获取已完成的当前操作失败的原因
- isCanceled():判断已完成的当前操作是否被取消
- addListener():注册监听器,当操作已完成的时候,将会通知指定的监听器。如果 Future 对象已经完成,则通知指定的监听器
认识编解码器
Java 的编解码器
- 编码(Encode)也叫序列化,它将对象序列化为字节数组,用于网络传输,数据持久化和其他用途。
- 解码(Decode)也叫反序列化,它把从网络、磁盘等读取的字节数组还原成原始对象(通常是原始对象的拷贝),方便后续的业务逻辑操作。如下图:
Java 序列化对象只需要实现 java.io.Serializable 接口并生成序列化 ID,这个类就能够通过 java.io.ObjectInput 和 java.io.ObjectOutput 序列化和反序列化。
我们知道序列化的目的就是为了将数据进行网络传输,或者进行对象持久化的操作。Java 序列化仅仅是 Java 编解码技术的一种,由于存在着种种缺陷,后面就衍生出了多种编码技术和框架,实现高效序列化的目的。Java 序列化存在一些问题:
- 无法跨语言
- 序列化后码流太大
- 序列化的性能太低
Netty 编解码器
- 概念:
在网络应用中需要实现某种编解码器,需要将原始字节数据与自定义的消息对象进行互相转换。网络中都是以字节码的形式来传输数据的。服务器编码数据后发送给客户端,客户端要对数据进行解码。
对于 Netty 而言,编解码器由两部分组成:编码器、解码器。
- 编码器:负责将消息从字节或其它序列化形式转成指定的消息对象(负责处理入站 InboundHandler 数据)
- 解码器:负责将消息对象转换成字节或其它序列形式在网络上传输(负责处理出站 OutboundHandler 数据)
- 编码器:Netty 的编(解)码器实现了 ChannelHandlerAdapter,也是一种特殊的 ChannelHandler,所以依赖 ChannelPipeline,可以将多个编解码器连接在一起,从而实现复杂的转换逻辑。
- 解码器:解码器负责解码(“入站”)数据从一种格式到另一种格式,解码器处理入站数据是抽象 ChannelInboundHandler 的实现。需要将解码器放在 ChannelPipline 中。对于解码器,Netty 主要提供了抽象基类 ByteToMessageDecoder 和 MessageToMessageDecoder 抽象解码器:
-
- ByteToMessageDecoder :将字节转换为消息,需要检查缓冲区是否有足够的字节
- ReplayingDecoder:继承 ByteToMessageDecoder,不需要检查缓冲区是否有足够的字节,但是 ReplayingDecoder 速度略慢于 ByteToMessageDecoder,同时不支持所有的 ByteBuff。项目复杂性高则使用 ReplayingDecoder,否则使用 ByteToMessageDecoder
- MessageToMessageDecoder:用于从一种消息解码为另一种消息(例如 POJO 到 POJO)
- 核心方法:
decode(ChannelHandlerContext ctx, Object msg, List out)