本章介绍EventLoop和EventLoopGroup

一、Netty的线程模型

1、Reactor单线程模型

所有的IO都在一个NIO线程上完成,

Netty学习(四)

由于Reactor使用的异步非阻塞IO,理论上可以处理所有的IO操作。但对于高负荷,大并发的场景,却不适合,主要原因有

  • 单NIO处理成千上百的链路,性能上无法支撑,几遍NIO的CPU达到100%,仍不能满足海量的消息编码、解码,接收和发送。
  • NIO线程负载过重后,会导致处理速度变慢,造成大量客户端连接超时后重发,会更进一步加剧消息积压
  • 可靠性问题,一旦NIO进入死循环或者异常后,整个通信系统都将不可用。

2、Reactor多线程模型

Netty学习(四) 

与单线程模型最大的区别是:

  • 有一个专门的线程Acceptor,处理客户端TCP的连接请求
  • 网络IO操作交给一个NIO线程池, 线程池中的线程处理链路的编码、解码,发送和接收
  • 一个NIO线程可以处理多个链路,但是一个链路只会属于一个NIO线程,防止并发操作引入的数据污染。

3、主从reactor多线程模型

在个别特殊情况下,一个acceptor线程处理高并发的连接请求,可能存在性能问题。比如,需要对客户端连接进行安全认证,认证本身是非常耗性能的。所以引入了主从模式的多线程模型。

Netty学习(四)

 服务端用于接收客户端连接请求不再是单一的NIO线程,而是一个线程池。

二、Netty中使用线程模型

Netty的线程模型并不是一直不变的,它取决于启动时的参数设置。Netty的最佳实践是

  • 创建两个EventLoopGroup,用于逻辑隔离Acceptor NIO和 NIO IO。
  • 尽量不要在channelHandler中启动用户线程
  • 解码要放在解码的Handler中,尽量不要放到用户线程中
  • 如果业务逻辑简单,没有复杂的业务计算,也没有可能会导致线程阻塞的磁盘、DB或者网络操作等,可以把业务逻辑放到NIO线程中完成,不要切换到用户线程
  • 如果业务逻辑复杂,尽量把业务逻辑派发到业务逻辑的线程池中,尽快释放Netty的NIO线程。

三、NIOEventLoop

Netty中NioEventLoop并不是一个纯粹的IO线程,除了负责IO的读写外,还负责

  • 系统task:通过调用NioEventLoop.execute(Runnable task)实现,主要是因为当IO线程和用户线程同时操作网络资源时,为了避免并发操作产生的锁竞争,将用户线程的操作封装成task,放到队列中,由IO线程负责执行,实现了局部无锁化。
  • 定时任务:通过调用NioEventLoop.schedule(Runnable task, long delay, TimeUnit unit).

Netty学习(四)

NIOEventLoop要处理IO读写事件, 内部聚合了一个Selector。

四、Netty的future和promise

Netty中所有的操作都是异步的,为了获取操作的结果,设计了ChannelFuture。ChannelFuture有两种状态,当一个IO操作开始时,会创建一个ChannelFuture,状态是unCompleted。一旦IO操作完成,会被设置为completed状态。

强烈建议通过增加ChannelFuture的监听器GenericFutureListener处理。通过GenericFutureListener代替get的原因是:异步IO时,如果不设置超时时间,有可能导致线程一直被挂起,甚至挂死;一旦设置超时时间,但时间有事不确定的,所以通过异步回调的方式最佳。

此外,不要在ChannelHandler中调用ChannelFuture.await()方法,可能会导致死锁,原因是:由IO线程负责异步通知发起IO操作的用户线程,如果IO线程和用户线程是同一个,就会导致IO线程等待自己通知完成操作,陷入相互等待。

Promise是可写的Future,因为Future并没有提供写方法,Netty通过promise对future扩展,用于设置IO操作的结果。

 

相关文章: