Netty-EventLoop和线程模型

任务调度

偶尔,需要调度一个任务以便稍后(延迟)执行或者周期性地执行。例如,你可能想要注册一个在客户端已经连接了5分钟之后才触发的任务。一个常见的用例,发送心跳消息到远程节点,以检查连接是否仍然还活着。如果没有响应,你便知道可以关闭该Channel了。

使用ScheduledExecutorService调度任务

1
2
3
4
5
6
class Task implements Runnable{
public void run(){
System.out.println("60 seconds later");
}
}
1
2
3
4
5
6
//创建一个具有10个线程的ScheduledExecutorService
ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
//调度任务在从现在开始的60秒之后执行
ScheduledFuture<?> future = executor.schedule(new Task(), 60, TimeUnit.SECONDS);
//一旦调度任务执行完毕, 就关闭ScheduledExecutorService以释放资源
executor.shutdown();

使用EventLoop调度任务

Netty使用Channel的EventLoop实现任务调度解决以下问题:ScheduledExecutorService的实现具有局限性,例如,作为线程池管理的一部分,将会有额外的线程创建。如果有大量的任务被紧凑地调度,那么这将会成为一个瓶颈。

使用EventLoop调度周期性的任务和取消任务

1
2
3
4
5
6
7
8
9
10
Channel ch = ...;
//调度在60秒之后,并且以后每间隔60秒运行
ScheduledFuture<?> future = ch.eventLoop().
scheduleAtFixedRate(new Task(), 60, 60, TimeUnit.SECONDS);
TimeUnit.SECONDS.sleep(10);
boolean mayInterruptIfRunning = false;
//取消该任务, 不让它再次运行, 并且设置运行中的任务不能被打断
future.cancel(mayInterruptIfRunning);

实现细节

线程管理

Netty线程模型的卓越性能取决于对当前执行的Thread的身份的确定,也就是,确定它是否分配给当前Channel以及它的EventLoop的那一个线程。每一个EventLoop将负责一个Channel的整个生命周期内的所有事件。

如果调用线程正是支撑EventLoop的线程,那么所提交的代码块将会被直接执行。否则,EventLoop将调度该任务以便稍后执行,并将它放入到内部队列中。当EventLoop下次处理它的事件时,它会执行队列中的那儿任务/事件。这也就解释了任何的Thread是如何与Channel直接交互而无需在ChannelHandler中进行额外同步的。

每个EventLoop都有它自己的任务队列,独立于任何其他的EventLoop。下图展示了EventLoop用于调度任务的执行逻辑。

点击加载

EventLoop/线程的分配

异步传输

异步传输实现只使用了少量的EventLoop(以及和它们相关联的Thread),而且在当前的线程模型中,它们可能会被多个Channel所共享。这使得可以通过尽量少的Thread的支撑大量的Channel,而不是每个Channel分配一个Thread。

点击加载

线程安全:一旦一个Channel被分配给一个EventLoop,它将会在它的整个生命周期都使用这个EventLoop(以及相关联的Thread)。

阻塞传输

点击加载

得到的保证,每个Channel的I/O事件都将会被一个Thread(用于支撑该Channel的EventLoop的那个Thread处理)。