Netty 是由 JBOSS 提供的一个 Java 开源框架,基于NIO事件驱动的网络应用框架
前言
- I/O 模型简单的理解:就是用什么样的通道进行数据的发送和接收,很大程度上决定了程
序通信的性能 - Java共支持3种网络编程模型/IO模式:BIO(传统阻塞型)、NIO非阻塞、AIO异步非阻塞
一、Java BIO
BIO(blocking I/O) : 同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连
接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造
成不必要的线程开销
二、Java NIO
- Java NIO 全称 java non-blocking IO,是指 JDK 提供的新API。
- NIO 相关类都被放在 java.nio 包及子包下,并且对原 java.io包中的很多类进行改写。
- NIO 有三大核心部分:Channel(通道),Buffer(缓冲区),Selector(选择器)
- NIO是 面向缓冲区 ,或者面向 块 编程的
- Java NIO的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得
到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞 - 通俗理解:NIO是可以做到用一个线程来处理多个Channel的。
三、Netty
1、引入Netty
- 原生NIO存在的问题
- NIO 的类库和 API 繁杂,使用麻烦。
- 需要具备其他的额外技能。
- 开发工作量和难度都非常大。
- JDK NIO 的 Bug
- Netty 对 JDK 自带的 NIO 的 API 进行了封装,解决了上述问题
- Netty5出现重大bug,已经被官网废弃了,目前推荐使用的是Netty4.x的稳定版
本
2、Reactor 模式
-
单 Reactor 单线程
-
单 Reactor 多线程
-
主从 Reactor 多线程
3、Netty模型
核心组件说明
- Netty 抽象出两组线程池,BossGroup 专门负责接收客户端连接,WorkerGroup 专门负
责网络读写操作。 - NioEventLoopGroup 下包含多个 NioEventLoop
- NioEventLoop 表示一个不断循环执行处理任务的线程,每个 NioEventLoop 都有一个
selector,用于监听绑定在其上的 socket 网络通道。采用串行化设计,从消息的读取->解码->处理->编码->发送。
• 每个 NioEventLoop 中包含有一个 Selector,一个 taskQueue
• 每个 NioEventLoop 的 Selector 上可以注册监听多个 NioChannel
• 每个 NioChannel 只会绑定在唯一的 NioEventLoop 上
• 每个 NioChannel 都绑定有一个自己的 ChannelPipeline
• 一个 Channel 包含了一个 ChannelPipeline,而 ChannelPipeline 中又维护了一个由 ChannelHandlerContext
组成的双向链表,并且每个 ChannelHandlerContext 中又关联着一个 ChannelHandler
• 入站事件和出站事件在一个双向链表中,入站事件会从链表 head 往后传递到最后一个入站的 handler,
出站事件会从链表 tail 往前传递到最前一个出站的 handler,两种类型的 handler 互不干扰
异步模型
- Netty 中的 I/O 操作是异步的,包括 Bind、Write、Connect 等操作会简单的返回一个
ChannelFuture。 - 调用者并不能立刻获得结果,而是通过 Future-Listener 机制,用户可以方便的主动获
取或者通过通知机制获得 IO 操作结果。Netty 的异步模型是建立在 future 和 callback 的之上的。
Netty的handler链的调用机制
四、Netty 核心源码剖析
-
Netty 启动过程源码剖析
- 创建2个 EventLoopGroup 线程池数组。数组默认大小CPU*2,方便chooser选择
线程池时提高性能 - BootStrap 将 boss 设置为 group属性,将 worker 设置为 childer 属性
- 通过 bind 方法启动,内部重要方法为 initAndRegister 和 dobind 方法
- initAndRegister 方法会反射创建 NioServerSocketChannel 及其相关的 NIO 的对象,
pipeline , unsafe,同时也为 pipeline 初始了 head 节点和 tail 节点。 - 在register0 方法成功以后调用在 dobind 方法中调用 doBind0 方法,该方法会 调
用 NioServerSocketChannel 的 doBind 方法对 JDK 的 channel 和端口进行绑定,
完成 Netty 服务器的所有启动,并开始监听连接事件
-
Netty 接受请求过程源码剖析
总体流程:接受连接----->创建一个新的NioSocketChannel----------->注册到一个worker EventLoop 上--------> 注册selecot Read 事件。
- 服务器轮询 Accept 事件,获取事件后调用 unsafe 的 read 方法,这个 unsafe 是 ServerSocket 的内部类,该方
法内部由2部分组成 - doReadMessages 用于创建 NioSocketChannel 对象,该对象包装 JDK 的 Nio Channel 客户端。该方法会像创建
ServerSocketChanel 类似创建相关的 pipeline , unsafe,config - 随后执行 执行 pipeline.fireChannelRead 方法,并将自己绑定到一个 chooser 选择器选择的 workerGroup 中的
一个 EventLoop。
-
Pipeline Handler HandlerContext创建源码剖析
- 每当创建 ChannelSocket 的时候都会创建一个绑定的 pipeline,一对一的关系,创建
pipeline 的时候也会创建 tail 节点和 head 节点,形成最初的链表。 - 在调用 pipeline 的 addLast 方法的时候,会根据给定的 handler 创建一个 Context,
然后,将这个 Context 插入到链表的尾端(tail 前面)。 - Context 包装 handler,多个 Context 在 pipeline 中形成了双向链表
- 入站方向叫 inbound,由 head 节点开始,出站方法叫 outbound ,由 tail 节点开始
-
Netty 心跳(heartbeat)服务源码剖析
-
Netty 核心组件 EventLoop 源码剖析
-
handler 中加入线程池和Context 中添加线程池的源码剖析
- 当我们在调用 addLast 方法添加线程池后,handler 将优先使用这个线程池,如果不添加,将使用 IO 线程
static void invokeChannelRead(finalAbstractChannelHandlerContext next, Object msg) {
final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRead(m);
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRead(m);
}
});
}
}
- 当耗时任务执行完毕再执行 pipeline write 方法的时候 ,最终会将write工作 交给IO 线程处理
private void write(Object msg, boolean flush, ChannelPromise promise) {
...
final AbstractChannelHandlerContext next = findContextOutbound(flush ?
(MASK_WRITE | MASK_FLUSH) : MASK_WRITE);
final Object m = pipeline.touch(msg, next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
if (flush) {
next.invokeWriteAndFlush(m, promise);
} else {
next.invokeWrite(m, promise);
}
} else {
final WriteTask task = WriteTask.newInstance(next, m, promise, flush);
if (!safeExecute(executor, task, promise, m, !flush)) {
task.cancel();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
小结:耗时业务可以异步执行,但IO读写始终在Worker Group的IO线程执行。
五、用Netty 自己 实现 dubbo RPC