这是《Netty实战》,《Netty权威指南》的读书笔记。
线程模型确定了代码的执行方式,所以理解所采用的并发模型的影响很重要。
线程池缓存和重用 Thread 极大地提高了性能,但它并不能消除由上下文切换所带来的开销,随着线程数量的增加,高负载下情况会变得越来越糟。接下来看看Netty是如何处理的。
EventLoop 接口
运行任务来处理在连接的生命周期内发生的事件是任何网络框架的基本功能。与之相应的编程上的构造通常被称为事件循环 — 一个 Netty 使用了 interface.io.netty.channel.EventLoop 来适配的术语。
EventLoop 由两部分API 组成:1,io.netty.util.concurrent 包,构建在JDK 的java.util.concurrent 包上,用来提供线程执行器;2,io.netty.channel 包的类,拓展了io.netty.util.concurrent 包里的类,用以与 Channel 的事件进行交互。
一个EventLoop将由一个固定不变的 Thread 驱动。所有的 I/O 操作和事件都由 EventLoop 的线程来执行,消除了多个 ChannelHandler 中进行同步的需要。
任务调度
对于 JDK 的任务调度类 ScheduledExecutorService 瓶颈在于,额外的线程创建开销是无法避免的,若大量任务被紧凑地调度,性能便会下降。那么Netty 是如何进行任务调度,如何解决该问题的?Netty 将任务封装成Runnable,放入阻塞队列中,EventLoop 的线程在事件循环中会从任务队列中取出任务并执行,始终都是由这一个线程来执行,也就没有线程创建切换的开销。
Channel ch = ...
ScheduledFuture<?> future = ch.eventLoop().schedule(
new Runnable() {
@Override
public void run() {
System.out.println("60 seconds later");
}
}, 60, TimeUnit.SECONDS);
Netty 的 NioEventLoop 的线程除了负责 I/O 的读写,还有任务的处理。任务有两种:系统任务 和 定时任务。
实现细节
每个 EventLoop 都有它自已的任务队列,独立于任何其他的 EventLoop。
注意不要将一个长时间运行的任务放入到执行队列中,因为它将阻塞需要在同一线程上执行的任何其他任务。如果必须要进行阻塞调用或者执行长时间运行的任务,建议使用一个专门的 EventExecutor,解释如下
ChannelHandler 的执行和阻塞:
通常 ChannelPipeline 中的每一个 ChannelHandler 都是通过它的 EventLoop(I/O 线程)来处理传递给它的事件的。所以至关重要的是不要阻塞这个线程,因为这会对整体的 I/O 处理产生负面的影响。
但有时可能需要与那些使用阻塞 API 的遗留代码进行交互。对于这种情况,ChannelPipeline 有一些接受一个 EventExecutorGroup 的 add()方法。如果一个事件被传递给一个自定义的EventExecutorGroup,它将被包含在这个 EventExecutorGroup 中的某个 EventExecutor 所处理,从而被从该Channel 本身的 EventLoop 中移除。对于这种用例,Netty 提供了一个叫 DefaultEventExecutorGroup 的默认实现。
EventLoop 线程的分配
不同的传输实现,EventLoop 的创建和分配方式也不同。这里介绍异步传输:使用少量的 EventLoop,它们被多个 Channel 共享。
EventLoopGroup 负责为每个新创建的 Channel 分配一个 EventLoop。在当前实现中,
使用顺序循环(round-robin)的方式进行分配以获取一个均衡的分布,并且相同的 EventLoop可能会被分配给多个 Channel。
一旦一个 Channel 被分配给一个 EventLoop,它将在它的整个生命周期中都使用这个
EventLoop(以及相关联的 Thread)。请牢记这一点,因为它可以使你从担忧你的 ChannelHandler 实现中的线程安全和同步问题中解脱出来。
另外,需要注意的是,EventLoop 的分配方式对 ThreadLocal 的使用的影响。因为一个
EventLoop 通常会被用于支撑多个 Channel,所以对于所有相关联的 Channel 来说,
ThreadLocal 都将是一样的。但是并非说它不可用,它仍然可以被用于在多个 Channel 之间共享一些重度的或者代价昂贵的对象,甚至是事件。
有了上面的基本认知,我们再从源码角度来看看其实现。Netty源码解析-NioEventLoop