Netty笔记(三)之EventLoop与线程模型
文章目录
netty版本
- netty版本:
io.netty:netty-all:4.1.33.Final
EventLoop
- 一个
EventLoop
由一个永远不会改变的Thread驱动,同时任务可以提交给EventLoop
实现,以立即执行或者调度执行。根据配置和CPU可用核心的不同,可能会创建多个EventLoop
实例用以优化资源的使用,并且单个EventLoop
可能会被指派用于服务多个Channel
。事件和任务是以先进先出的顺序执行,这样可以保证字节内容总是按正确的顺序被处理,消除潜在的数据损坏的可能性 - 在Netty4中所有的I/O操作和事件都由已经被分配给了
EventLoop
的那个Thread来处理。 - Netty线程模型的卓越性能取决于对于当前执行的Thread的身份的确定(通过调用
EventLoop
的inEventLoop(Thread)
来实现),EventLoop
将负责处理一个Channel
的整个生命周期内的所有事件 - 如果当前调用的线程正是支撑
EventLoop
的线程,那么所提交的代码块将会被直接执行。否则EventLoop
将调度该任务以便稍后执行,并将它放入到内部队列中。当EventLoop
下次处理它的事件时,它会执行队列中的那些任务/事件。每个EventLoop
都有它自己的任务队列,独立于任何其他的EventLoop
- 永远不要将一个长时间运行的任务放入到执行队列中,因为它将阻塞需要在同一个线程上执行的任何其他任务。如果必须要进行阻塞调用或者执行长时间运行的任务,建议使用一个专门的
EventExecutor
-
EventLoop
、Channel
与EventLoopGroup
之间的关系- 一个
EventLoopGroup
包含一个或者多个EventLoop
,EventLoop
就是一个Channel
执行实际工作的线程。 - 一个
EventLoop
在它的声明周期内只和一个Thread绑定 - 所有由
EventLoop
处理的I/O事件都将在它专有的Thread上被处理 - 一个
Channel
在它的生命周期内只注册于一个EventLoop
,在Netty I/O操作中,你的程序不需要同步,因为一个指定Channel
的所有I/O始终由同一个线程来执行 - 一个
EventLoop
可能会被分配给一个或多个Channel
- 一个
异步传输
- 异步传输实现只使用了少量的
EventLoop
(以及和它们相关联的Thread),而且在当前的线程模型中,它们可能会被多个Channel
所共享。这使得可以通过尽可能少量的Thread
来支撑大量的Channel
,而不是每个Channel
分配一个Thread
。 -
EventLoopGroup
负责为每个新创建的Channel
分配一个EventLoop
。 在当前实现中, 使用顺序循环( round-robin )的方式进行分配以获取一个均衡的分布,并且相同的EventLoop
可能会被分配给多个Channel
。一旦一个Channel
被分配给一个EventLoop
,它将在它的整个生命周期中都使用这个EventLoop
(以及相关联的 Thread)。所以不需要担心ChannelHandler的线程安全问题 -
EventLoop
的分配方式对ThreadLocal
的使用的影响 。 因为一个EventLoop
通常会被用于支撑多个Channel
,所以对于所有相关联的Channel
来说,ThreadLocal
都将是一样的。这使得它对于实现状态追踪等功能来说是个糟糕的选择。然而,在一些无状态的上下文中,它仍然可以被用于在多个Channel
之间共享一些重度的或者代价昂贵的对象,甚至是事件。
阻塞传输
- 对于阻塞IO来说,每一个
Channel
都将被分配给一个EventLoop
(以及它的 Thread)。保证每个Channel
的I/O事件都将只会被一个Thread
(用于支撑该Channel
的EventLoop
的那个Thread)处理
Netty多线程最佳实践
- 服务端创建两个
NioEventLoopGroup
,用于隔离NIO Acceptor
和NIO I/O线程
- 尽量不要在
ChannelHandler
中启动用户线程(解码后用于将POJO消息派发到后端业务线程的除外) - 解码要放在NIO线程调用的解码Handler中进行,不要切换到用户线程中完成消息解码
- 如果业务逻辑操作非常简单,没有复杂的业务逻辑计算,没有可能会导致线程被阻塞的磁盘操作、数据库操作、网络操作等,可以直接在NIO线程上完成业务逻辑编排,不需要切换到用户线程
- 如果业务逻辑处理复杂,不要在NIO线程上完成,建议将解码后的POJO消息封装成Task,派发到业务线程池中由业务线程执行,以保证NIO线程尽快释放,处理其他的I/O操作