Netty的线程模型以及玩法

事件循环组

 

Netty中所有的I/O操作都是异步的,通过ChannelFuture来获取异步执行结果。

异步执行依赖一个线程池EventLoopGroup,它继承了ExecutorService。

这玩意底层是个数组,存放一堆EventLoop,而EventLoop继承了EventExecutor,所以EventLoopGroup的execute()方法可以不断调用next()丢给下一个线程执行,就是个线程池,全异步。

BoosGroup&WorkGroup

web系统的I/O事件无非read/write/connect/accept几种。

基于React模型,Netty可以设置bossGroup和workGroup两个EventLoopGroup,其中的EventLoop(线程)数量可以根据需要调节配置。而boosGroup和workGroup的分工不同。

boosGroup用于监听和处理客户端连接的accept事件,而workGroup负责read/write事件处理。

 

Netty架构中,在最前面负责xxx客户端连接的是Acceptor,在server端开发时,目前只有一个实现ServerBootstrapAcceptor,是ServerBootstrap的内部类,继承ChannelHandlerAdapter对象,也就是个ChannelHandler。

ServerBootstrap的启动过程是——在init()方法底层会调用doBind0方法,其中会触发EventLoopGroup.execute()方法,EventLoop线程执行nio select后,进而触发processSelectedKey()方法,当监听到read/accept事件时,会出触发pipeline中每个ChannelHandler的channelRead()方法,就会触发ChannelHandlerAdapter的channelRead()。

 

 Acceptor的功能,即ChannelHandlerAdapter的channelRead()方法实现——这玩意逻辑就是把接收到的客户端连接(NioSocketChannel),通过workGroup异步注册到其中一个EventLoop关联的Selector(多路复用器)上,后续当该连接有读事件就绪时都会由同一个NioEventLoop处理——select出来并丢给child pipeline去执行。

 

ChanelPipeline原理

ChannelPipeline数据结构是一个双向链表,其中有两个属性head、tail分别指向链头和链尾;

ChannelPipeline的每个元素是一个ChannelHandlerContext,其中有next和prev分别指向前后元素;

每个ChannelHandlerContext中持有一个ChannelHandler的引用,他们负责具体的I/O事件处理。

典型的责任链模式,ChannelPipeline.fireChannelRead()会触发head节点开始read(),在Handler中通过调用context.fireChannelRead()来调度责任链中的下一个节点执行read().

如果不调用,则当前handler执行完就结束。

类似与javax.Filter。

 

 pipeline中对执行链路的调度都是单线程的,但是connect、write等方法都是异步的,会调用workGroup.execute()并返回一个ChannelFuture对象。

Netty 的 NioEventLoop 读取到消息之后,直接调用 ChannelPipeline 的fireChannelRead (Object msg)。 只要用户不主动切换线程, 一直都是由NioEventLoop 调用用户的 ChannelHandler,期间不进行线程切换。这种串行化处理方式避免了多线程操作导致的锁的竞争,从性能角度看是最优的。

读/写事件处理

读事件与accept事件处理基本一致,就是WorkGroup的EventLoop执行selector I/O事件轮询,如果有读事件,调用Unsafe完成读数据,并把读到的数据丢到每个channel对应的pipeline去执行。

关于写事件,在ChannelHandler中可以通过ChannelHandlerContext调用write方法写数据,这是全异步的。

 WorkGroup中select到有WRITE事件就绪时,就会调用channel.unsafe().forceFlush(),底层最终调用doWrteMessage()通过javaChannel将数据写出,标准的NIO玩法。

 

玩法

(1) 时间可控的简单业务直接在 I/O 线程上处理

时间可控的简单业务直接在 I/O 线程上处理,如果业务非常简单,执行时间非常短,不需要与外部网络交互、访问数据库和磁盘,不需要等待其它资源,则建议直接在业务 ChannelHandler 中执行,不需要再启业务的线程或者线程池。避免线程上下文切换,也不存在线程并发问题。

(2) 复杂和时间不可控业务建议投递到后端业务线程池统一处理

复杂度较高或者时间不可控业务建议投递到后端业务线程池统一处理,对于此类业务,不建议直接在业务 ChannelHandler 中启动线程或者线程池处理,建议将不同的业务统一封装成 Task,统一投递到后端的业务线程池中进行处理。过多的业务ChannelHandler 会带来开发效率和可维护性问题,不要把 Netty 当作业务容器,对于大多数复杂的业务产品,仍然需要集成或者开发自己的业务容器,做好和Netty 的架构分层。

(3) 业务线程避免直接操作 ChannelHandler

业务线程避免直接操作 ChannelHandler,对于 ChannelHandler,IO 线程和业务线程都可能会操作,因为业务通常是多线程模型,这样就会存在多线程操作ChannelHandler。为了尽量避免多线程并发问题,建议按照 Netty 自身的做法,通过将操作封装成独立的 Task 由 NioEventLoop 统一执行,而不是业务线程直接操作

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值