为了和之前的Wangle源码分析系列形成呼应,现在将以前写的Netty的图解过程搬移过来。以下所绘制图形均基于Netty4.0.28版本。
一、connect(outbound类型事件)
当用户调用channel的connect时,会发起一个outbound类型的事件,该事件将在pipeline中传递(pipeline.connect),首先由tail handler处理,该handler只是将事件透传给下一个outbound类型的用户Handler(如果有),事件依次传递下去,直到传递到head handler,该handler会调用unsafe.connect()向eventloop(nio的selector)注册一个读事件。
二、connect成功之后数据流图(inbound类型事件)
当用户发起一个connect请求后,当连接可用时,eventloop(底层使用nio的selector)会引发一个ChannelActive事件,该事件最先有unsafe捕获,之后会调用pipeline.fireChannelActive()将该事件在pipeline中传播,紧接着会根据inbound类型事件的传递方式在各个handler和context之间进行链式传递。其中,有一个比较关注的地方是,在channelActive事件触发的时候,如果channel被设置成autoRead,那么此时还会调用channel.read()方法,该方法并不是真正的从channel读取数据,而是向eventloop注册读事件(因为一个channel在向eventloop中注册时,默认不注册任何事件),关于channel.read的过程可以看下文的另一张图。
三、channel.read事件流图(outbound类型事件)
当用户调用channel.read()后,会发起一个outbound类型的事件,该事件最先会由pipeline中tail handler处理,该handler只是将该事件透传给前面一个outbound类型的用户handler(如果有的话),这样依次继续向前传递,直到传递到head handler,该handler会调用unsafe.read()向eventloop注册读事件(也就是向nio的selector上添加读事件)。
四、channel.write(outbound类型事件)
与channel.read()类型相同,wirte也是一个outbound类型事件,该事件最先会由pipeline中的tail handler透传给前面的一个outbound类型的用户handler(如果有的话),这样依次传递,直到传递给head handler,该handler会调用unsafe.write()方法,这里的wirte并不会执行真正的发送,而是将要发送的数据缓存起来,直到调用flush时,这些数据才会执行真正的网络io。这里需要注意,如果调用write的线程不是当前channel绑定的io线程(EventLoop),那么在write内部会判断,并将写操作封装成一个task强行加入io线程(EventLoop)的队列,因此后续的write操作就是在io线程中执行的。
五、flush(outbound类型事件)
如前文所示,flush也是一个outbound类型的事件,与wirte不同,flush会执行真正的网络io操作。
六、当channel有数据可读时(inbound类型事件)
当eventloop层检测到网络层有数据可读时(nio的selector返回相应的seleciontKeys),该事件会首先传递给unsafe,紧接着会调用pipeline.fireChannelRead(),将事件开始在pipeline中传递,该事件最先会有head handler处理(head.fireChannelRead()),该handler直接将事件透传给下一个inbound类型的用户handler(如果有的话),该事件依次向下传递,直到传递到tail handler。这里需要注意,因为所有的handler默认都是在io线程中串行执行的,因此如果某个handler比较耗时,就可能导致io线程阻塞,从而导致绑定在这个io线程(EventLoop)上的所有channel上的事件得不到处理,引起各种超时,为此,可以为这样的handler单独设置一个线程池EventExecutorGroup,这样当执行到这个handler时,将从EventExecutorGroup取一个线程执行,但是此时要注意handler之间的共享访问问题。
最后,盗用一张网络的图,从整体上介绍一下Netty的线程模型,便于理解。
1、Boss Group:作为服务端 Acceptor 线程,用于 accept 客户端链接,并转发给 WorkerGroup 中的线程。Netty中虽然也可以将Boss Group设置为多个,但是在执行bind的时候只会绑定其中的一个,而不是同时绑定所有(类似nginx以前的惊群效应)。
2、Worker Group:作为 IO 线程,负责 IO 的读写,从 SocketChannel 中读取报文或向 SocketChannel 写入报文。
3、Task Queue/Delay Task Queue:作为定时任务线程,执行定时任务,例如链路空闲检测和发送心跳消息等。
4、ExecutorGroup:业务自定义线程池,处理一些耗时任务。