Netty EventLoop 和 EventLoopGroup
Netty的线程模型
1.1主从的 Reactor多线程模型
服务端用于接受客户端的连接不再是一个单独的NIO线程,而是一个独立的NIO线程池,Accertor接收到客户端的TCP连接请求之后,将新创建的SocketChannel注册到 I/O 线程池 netty使用的是这种。
服务端启动的时候,创建了两个NioEventLoop,它们是两个独立的Reactor线程池,一个用于接受TCP的连接,另一个用于处理I/O相关的读写操作,或者执行系统的Task,定时任务Task。
用于接受客户端连接的线程池职责是:
- 接受客户端的连接,初始化Channel参数
- 将链路状态变更事件 通知给ChannelPipline
用于处理I/O操作的线程池职责如下:
- 异步读取通信对端的数据包,发送读事件到ChannelPipline;
- 异步发送消息到通信对端,调用ChannelPipline的消息发送接口;
- 执行系统的Task;
- 执行定时的Task,例如链路空闲检测定时任务;
1.2 基于该线程模型的最佳实践
- 创建两个NioEventLoopGroup 用于隔离Nio Acceptor和Nio I/O 线程;
- 尽量不要再ChannelHandler 中启动用户线程(解码后用于将POJO 消息派发到后端服务端除外);
- 解码要放在Nio的解码Handler 中进行,不要切换到用户线程中解码;
- 如果业务逻辑非常简单,没有导致线程阻塞的磁盘操作,数据库操作,网络操作,可以在NIO线程完成业务处理;
- 如果业务逻辑比较复杂,不要再NIO线程完成,建议将解码后的pojo消息封装为task,派发到业务线程池中有业务线程处理,保证Nio线程尽快被释放,处理其他的I/O操作。
1.2.1 推荐的线程数量计算公式
- 线程数量 = (线程总时间 / 瓶颈资源时间)* 瓶颈资源的线程并行数
NioEventLoop的源码分析
2.1 NioEventLoop的设计原理
NioEventLoop 并不是一个纯粹的I/O线程 ,除了负责I/O操作,还兼顾处理一下任务:
- 系统Task:通过调用execute(runable)方法实现,Netty有很多的系统task,创建他们的主要原因是:当I/O操作线程和用户线程同时操作网络资源时,为了防止并发导致锁竞争,将用户的线程的操作封装为task 放入消息对列中,让I/O线程去处理,这样实现了局部无锁。
- 定时任务:调用schedule(runable,delay,timeunit)来实现。
继承类图
NioEventLoop
2.2run方法
2.3 出现bug的原因
服务端等待连接,客户端发起连接,发送消息,服务端接受连接,并注册监听通道的OP_READ,服务端读取消息,从感兴趣事件集合中移除OP_READ,客户端关闭连接,服务端给客户端发送消息,服务端select方法不再阻塞,无限唤醒并且返回值为0.
2.4 轮循到就绪状态SocketChannel 处理I/O
processSelectedKey 方法分析