Netty学习之EventLoop&Threading Model
前言
在前面我们学习了Netty的众多组件,如ByteBuf、Channel、ChannelHandler、ChannelPipeline等,这些组件组合起来具有神奇的魔力,而EventLoop则是赋予它们魔力的源泉,EventLoop是Netty的线程模型,也是Netty的心脏,本小节我们就来学习EventLoop相关的内容。
EventLoop
事件循环Event Loop:在连接的生命周期中,通过运行任务来处理事件(其实就是循环扫描事件列表,然后根据出现的事件来进行处理,单词Event Loop),EventLoop的基本原理如下
while(!terminated) {
// 等待事件的到来
List<Runnable> readyEvents = blockUtilEventsReady();
// 如果有事件,则进行处理
for(Runnable ev: readyEvents) {
ev.run();
}
}
在Netty的模型中,一个EventLoop对应一个线程,然后任务能够直接提交到EventLoop的实现去直接或者参与调度,根据CPU核的数量,可能同时有多个EventLoop创建出来用于提高性能的资源利用率,单一的一个EventLoop能够服务多个Channels
这里需要注意的是,一个Channel只会跟一个EventLoop绑定,但是一个EventLoop可以服务于多个Channels,所以并不是一对一的关系,这也是Netty能够减少线程使用数量,从而提高资源利用率的原因之一。
Netty中的EventLoop继承于ScheduledExecutorService,并且定义了一个parent()
方法,通过该方法可以获得当前EventLoop实现实例所归属的EventLoopGroup实例
Netty中的任务以及事件是以FIFO的顺序执行,这样可以保证数据流能够以正确的顺序进行处理。
一个Event的类型通常决定了其会被如何处理,如传输数据到用户层、处理数据等,为了使得事件处理逻辑更通用以及灵活,Netty4中所有的I/O操作以及事件被EventLoop所属的线程处理,其具体实现是判断当前触发I/O操作的线程是否是当前channel所对应的EventLoop所对应的线程,如果是的话,则执行该任务,如果不是的话,则将其放入对应的队列中,等待稍后对应的线程来执行,从而保证了一个channel中的事件都是由同一个线程来处理,减少了同步锁所带来的开销,这也是前面提到的,Channel是线程安全的,可以被其他线程所操作的原因,EventLoop执行逻辑如下图所示(图片来自《Netty in action》,下同)。
任务调度
在JDK中,可以通过单线程的Timer类以及线程池对象ScheduledExecutorService
来执行,但该方式在负载比较重的情况下,会出现性能问题,额外的线程用于维护线程池,当多个线程同时调度时,会出现瓶颈。
在Netty中,通过使用EventLoop来实现调度功能
Channel ch = ...;
ScheduledFuture<?> future = ch.eventLoop().schedule(
new Runnable() {
// ops
}, 60, TimeUnit.SECONDS);
EventLoop分配
EventLoop从EventLoopGroup中申请,EventLoopGroup可以理解为EventLoop的容器(类似于线程跟线程池的关系),可以根据需要从EventLoopGroup获取EventLoop(及其绑定的线程)。
异步的实现使用少量的EventLoops,并且可能在多个Channel中共享,这种方式允许少量的线程来服务多个Channel,而不是每个Channel分配一个线程,一旦一个Channel被赋予一个EventLoop,在其生命周期中就只会使用该EventLoop及其绑定的线程,所以在同一个Channel中,不需要过多担心线程安全问题(只有一个线程嘛)。
EventLoopGroup、EventLoop、Channel之间的关系如下图所示(非阻塞模式,即NIO、AIO)
阻塞实现,对于传统的OIO,由于会发生阻塞,所以不能复用EventLoop(一个EventLoop对应一个线程),Netty采用的是每个EventLoop对应一个Channel
总结
本小节我们学习了Netty的EventLoop及相关内容,包括EventLoop与线程、Channel的关系,EventLoop与EventLoopGroup的关系等,由于Netty中的Channel与唯一的EventLoop绑定,而EventLoop与唯一的线程绑定,所以,在使用的时候需要注意,不能在一个EventLoop中执行阻塞操作,否则会影响其他Channel的处理,与此同时,由于EventLoop的设计,Channel是线程安全的,可以在多线程环境中使用。