Netty是一款高效的NIO框架和工具,基于Java NIO实现,Java NIO的Selector给Reactor模式提供了基础,netty结合Selector和Reactor模式实现自身高效的线程模型。本文先对Reactor模式的多线程和主从模型进行简单介绍,而后结合netty中的几个重要组件对netty线程模型和工作流程进行简单分析。这里可以参看李林锋的文章。
多线程Reactor
与单线程不同的地方,在于多线程Reactor模型将监听连接和连接的I/O操作放到了不同的线程里面,看下面的示意图:
它的主要特点如下:
- 有专门的Acceptor线程用于监听、接收客户端的连接请求
- 网络I/O的读写操作则由另外的线程池负责,包含一个任务队列和多个线程
- 一个NIO线程可以处理多个链路,但每个链路只对应一个线程
大部分情况下,一个线程负责监听和处理所有的客户端都能满足需要,但在特殊场景下,如并发量过高,或者在连接接入的时候需要进行安全认证等问题,单个Acceptor线程不足以满足性能,这时候就需要主从Reactor模型。
主从多线程Reactor
服务端用于接收连接的不再是一个单独的NIO线程,而是一个独立的线程池:
工作流程:
- 从主线程池中选择一个Reactor线程作为Acceptor线程,绑定监听端口,接收客户端连接
- 接收到连接请求后,将其注册到主线程其他线程上,由它们负责接入认证等工作
- 链路建立完成后,就链路从主线程池的多路复用器上摘除,重新注册到Sub线程池的NIO线程上,负责后续IO操作
netty的线程模型不是一成不变的,通过对启动参数的配置,netty可以支持多种Reactor线程模型,最常用的是多线程模型。
下面说一下netty中几个重要的组件。
Selector
Selector是Java NIO提供的多路复用器,负责配合操作系统的select/epoll操作将就绪的IO事件分离出来,落地为SelectionKey,我们可以将SelectionKey看做Reactor模式中的资源
EventLoop/EventLoopGroup
EventLoopGroup其实就是一个EventLoop线程组,netty中通常有多个EventLoop同时工作,每个EventLoop维护着一个Selector实例(类似单线程Reactor工作)。如果没有显式指定,默认每个EvenLoopGroup中的线程数为可用的CPU内核数*2。
通常每个netty服务端有两个EventLoopGroup。
一个用作Acceptor线程池,负责处理客户端的连接请求,通常一个服务端口对应一个EventLoop线程,根据实际需要配置线程组的线程数量。Acceptor线程通过不断轮询Selector上的Accept事件,将accept的SocketChannel交给另外一个EventLoop线程组。
另一个EventLoopGroup会根据线程组的顺序next一个可用的EventLoop将这个SocketChannel注册到其维护的Selector上,并处理其后续的I/O的事件。
ChannelPipleline
每个SocketChannel都有一个Pipleline实例,而每个Pipleline中维护了一个ChannelHandler链表队列。Pipleline和ChannelHandler的关系类似servlet和filter过滤器的作用。EventLoop从Selector中分离出就绪的channel以后,会将它传递的消息传输到Pipleline中,通过ChannelHandler处理链进行层层处理,用户可以在Handler中添加自己的业务逻辑。
ChannelPipleline中本身维护着两个不可见的HeadHandler和TailHandler,head靠近网络层,tail靠近用户。netty中有两类事件类型,inbound和outbound。inbound可以理解为从网络数据外部流向内部,如读取消息;outbound为网络数据从内部流向外部,如写消息。Pipleline会根据事件的类型, 自上而下或自下而上调用事件相关联的ChannelHandler对消息进行处理。如读取消息的时候会依次执行HeadHandler、ChannelHandler1...ChannelHandlerN、TailHandler。
写在最后
通过以上几个组件的作用,应该可以对netty的工作流程有个大体的认识。其实笔者接触netty是从Vert.x开始的,netty可能更偏向网络传输这个层面,一些RPC框架底层传输用的也是netty。
netty中还有其他要点,比如粘包拆包、缓冲区这些问题笔者还没有认真研究过,待有时间再研究源码吧,感叹时间真不够用啊!