netty是如何工作的:EventLoopGroup是做什么用的?

上一篇文章中,我们了解了如何通过 netty 实现一个echo server。你应该还记得 ServerBootstrap 启动类,它负责 server 的启动管理,在启动前我们需要为其配置 EventLoopGroupEventLoopGroup 有配套的 ServerSocketChannel,比如通常使用最多的是 NioEventLoopGroup,它就需要和 NioServerSocketChannel 搭配起来工作。

1
2
3
4
// NioServerSocketChannel和NioEventLoopGroup搭配干活
EventLoopGroup group = new NioEventLoopGroup();
b.group(group) // 配置EvnetLoop
.channel(NioServerSocketChannel.class) // 配置ServerSocketChannel

你可能会有疑问:EventLoopGroup 是什么?它的作用什么?本文就来探讨下这个问题。

要使用简单的方式讲清楚这个问题,确实有一定的难度。我想了好久,最后决定从 EventLoop 的用法讲起,逐步去深入分析。

因此,我们暂且搁置下 EventLoopGroup,先来看一下 EventLoop 的常见用法。如果你看过 netty 的源码,你将会看到很多类似下面的代码:

1
2
3
4
5
6
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
// 忽略具体逻辑
}
});

这段代码看起来很简单:首先从 Channel 中获取到 EventLoop,然后通过 execute 方法往 EventLoop 中提交一个 runnable 任务,直观上的感觉就像使用线程池一样。

这里面有两个问题:1.ChannelEventLoop 的关系是什么?2.runnable 是提交到线程池里了吗?它是如何通过 EventLoop 执行的呢?

先来看第一个问题:ChannelEventLoop 的关系是什么?从 AbstractChannel 的实现中可以看到,eventLoop 是它的一个属性,也就是说,Channel 上绑定了一个 EventLoop 对象

EventLoop-Channel与EventLoop的关系

1
2
3
4
5
6
7
8
9
private volatile EventLoop eventLoop;

public EventLoop eventLoop() {
EventLoop eventLoop = this.eventLoop;
if (eventLoop == null) {
throw new IllegalStateException("channel not registered to an event loop");
}
return eventLoop;
}

再来看第二个问题:任务是如何在 EventLoop 中执行的?

提交任务到EventLoop中执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void execute(Runnable task, boolean immediate) {
boolean inEventLoop = inEventLoop();
addTask(task); // 添加任务到队列中
if (!inEventLoop) {
startThread(); // 启动线程
// ignore
}
// ignore
}

protected void addTask(Runnable task) {
if (!offerTask(task)) {
reject(task);
}
}

final boolean offerTask(Runnable task) {
if (isShutdown()) {
reject();
}
return taskQueue.offer(task); // 添加到队列中
}

当我们往 EventLoop 中提交 runnable 任务的时候,它首先会提交到 SingleThreadEventExecutor 的一个任务队列中,SingleThreadEventExecutor 按照一定的规则,在合适的时候将任务取出来执行。

SingleThreadEventExecutor 是什么?它是所有 EventLoop 实现类的一个父类,用于通过单线程的方式执行所有提交过来的任务。

对上面的分析做一个总结:

channel 上绑定了一个 EventLoop 实例,它以单线程的方式执行任务。外部程序调用 execute 方法将任务提交到 EventLoop 的队列中排队,所有的任务按提交的顺序执行。

EventLoop 只做了这些事吗?当然不是,不要忘了,netty 是一个网络框架,它还要处理网络数据的收发。我们以 NioEventLoop 为例接着分析 EventLoop 的职责。

每一个 EventLoop 都需要实现 SingleThreadEventExecutorrun 方法,它里面用于执行具体实现类的业务逻辑。例如 DefaultEventLoop 就只是取出任务并执行

1
2
3
4
5
6
7
8
9
10
11
12
13
protected void run() {
for (;;) {
Runnable task = takeTask();
if (task != null) {
task.run();
updateLastExecutionTime();
}

if (confirmShutdown()) {
break;
}
}
}

对于 NioEventLoop 来说,它的执行逻辑如下:

  1. 执行 select 操作
  2. 处理已就绪的 IO 事件
  3. 根据设置的 IO 比例、第 2 步的执行耗时这两个因素计算本次处理任务的时间,从队列中获取任务并执行

NioEventLoop的执行逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
for (;;) {
// select操作
strategy = select(curDeadlineNanos);

if (ioRatio == 100) {
try {
if (strategy > 0) {
// 处理网络IO事件
processSelectedKeys();
}
} finally {
// 执行队列中的任务
ranTasks = runAllTasks();
}
} else if (strategy > 0) {
final long ioStartTime = System.nanoTime();
try {
// 处理网络IO事件
processSelectedKeys();
} finally {
// 按时间比例执行队列中的任务
final long ioTime = System.nanoTime() - ioStartTime;
ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
} else {
// 无IO事件,则执行队列中的任务
ranTasks = runAllTasks(0); // This will run the minimum number of tasks
}
}

讲到这里,你应该对 EventLoop 做的事有所了解了。回到开头的问题,EventLoopGroup 是做什么的?它和 EventLoop 是什么关系?

我们接着来看。前面说过,每个 channel 上会绑定一个 EventLoop 实例,那么它是怎么绑定上去的呢?

在通过 ServerBootStrap 启动 server 时,它会将初始化好的 channel 注册到 EventLoopGroup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
init(channel);
} catch (Throwable t) {
// ignore
}

// 关键在这里,往EventLoop上注册channel
ChannelFuture regFuture = config().group().register(channel);

// ignore
}

EventLoopGroup 所谓的注册 channel,其实就是通过一定的策略选择一个 EventLoop,然后把 channel 注册到 EventLoop 上,最终会执行到如下代码,完成 channelEventLoop 关系的绑定

1
AbstractChannel.this.eventLoop = eventLoop;

EventLoop与Channel联系

最后做个总结:server 启动时,需要从用户配置的 EventLoopGroup 中选择一个 EventLoop 并且和 channel 绑定,一个 channel 只会绑定到一个 EventLoop 上。后续 EventLoop 负责处理该 channel 上的网络 IO 事件和 其他提交过来的任务,所有任务按提交的顺序执行。

EventLoop是做什么的