§1 作用
Netty 是一个以 性能至上 为第一原则的框架
但 Netty 实际上还是一个基于 NIO 的框架,它依然只是 非阻塞 而不是 异步
这里说的 异步 与否,是指系统底层 IO 模型是否阻塞,而不是接口调用方式
常说的 Netty 是异步非阻塞其实都是其接口调用方式说的,参考 基础 | NIO - [IO 模式的演进]
Netty 在实现时并没有广泛而成熟的使用 AIO (NIO 2.0)
这是因为 linux 上 AIO 技术依然不够成熟,不能良好的支持所有场景
同步意味着依然会在系统进程中阻塞,因此 Netty 会通过轮询的方式挑出可处理的业务进行处理
达到了 除了必要的系统阻塞开销外,尽量避免其他阻塞 的目的,如下图
- 可以在基于 NIO 的模式下,系统阻塞是无法避免的阻塞,是真实处理 IO 就绪的时间
- 通过轮询,可以是处理任务的线程忽略卡在系统阻塞的任务,而是处理可以被处理任务
- 这么做的核心思路是:用户线程便宜行事,能处理谁处理谁,能处理多少处理多少,尽量压榨用户线程的所有时间
- 再结合接口异步,达到了 伪异步 的接口手感和性能
但是,Netty 中处理连接 NioSocketChannel
中业务时,可能遇到 业务造成的阻塞,比如业务处理时间本身比较长
这相当于在必要的系统阻塞(仅限于系统读写的必要时间)外,额外引入了新的阻塞
为了维护并实践 Netty 效率第一的原则,这种业务通过 taskQueue / scheduledTaskQueue 转为异步处理
- taskQueue
- 用于异步处理
- 在
NioEventLoop
中,继承自SingleThreadEventExecutor
中 - 是一个队列
Queue<Runnable>
- scheduledTaskQueue
- 用于异步处理
- 在
NioEventLoop
中,继承自AbstractScheduledEventExecutor
中 - 是一个优先级队列
PriorityQueue<ScheduledFutureTask<?>>
而 taskQueue / scheduledTaskQueue 的存在,变相证明 Netty 是非异步的。非异步,意味着即使从操作系统层面而言,也是几乎所有的 CPU 时间都没有浪费。因此,塞与非阻塞对于应用的吞吐量已经没有什么影响了,也因此,没有必要加入任务队列
§2 使用场景
用户程序自定义普通任务
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.channel().eventLoop().execute(()->{
try {
TimeUnit.SECONDS.sleep(10);
ByteBuf buf = (ByteBuf) msg;
System.out.println(buf.toString(CharsetUtil.UTF_8));
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("client read done...");
}
用户程序已定义定时任务
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.channel().eventLoop().schedule(()->{
try {
TimeUnit.SECONDS.sleep(10);
ByteBuf buf = (ByteBuf) msg;
System.out.println(buf.toString(CharsetUtil.UTF_8));
} catch (InterruptedException e) {
e.printStackTrace();
}
},3,TimeUnit.SECONDS);
System.out.println("client read done...");
}
非当前 Reactor 线程调用 Channel
的各种方法
如推送业务:
- 客户端连接服务端时,服务端记录客户端的 hashcode
- 根据 hashcode 维护
SocketChannel
容器 - 按用户关注或喜好生成推送内容,连同对应的 hashcode 推送到
taskQueue
- 按 hashcode 从
SocketChannel
容器中找到对应的SocketChannel
,发送内容