一.Netty的实现原理
Netty是基于JAVA的NIO技术实现的。NIO的原理是使用I/O多路复用技术,通过把多个I/O的阻塞复用到同一个select的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。
多路复用器Selector是NIO编程的基础,它提供了选择已经就绪任务的能力,它会不断轮询注册在其上Channel,如果某个Channel上有新的连接接入、读和写事件,这个Channel就处于就绪状态,会被Selector轮询出来,进行后续I/O操作。
一个多路复用器Selector可以同时轮询多个Channel,由于JDK使用了epoll()代替传统的select实现,因此它没有最大连接句柄1024/2048限制。这就意味着,只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端。
二.Netty有以下一些重要组件
-
Bootstrap or ServerBootstrap:一个Netty应用通常由一个Bootstrap开始,它主要作用是配置整个Netty程序,串联起各个组件。
-
EventLoop:可以认为是一个线程,它的目的是为Channel处理IO操作,一个EventLoop可以为多个Channel服务。
-
EventLoopGroup:可以认为是一个承载EventLoop线程池。
-
ChannelPipeline:一个Netty应用基于ChannelPipeline机制,这种机制需要依赖于EventLoop和EventLoopGroup,因为它们三个都和事件或者事件处理相关。
-
Channel:代表了一个Socket链接,或者其它和IO操作相关的组件,它和EventLoop一起用来参与IO处理。
-
Future or ChannelFuture:在Netty中所有的IO操作都是异步的,因此,你不能立刻得知消息是否被正确处理,但是我们可以过一会等它执行完成或者直接注册一个监听,具体的实现就是通过Future和ChannelFutures,他们可以注册一个监听,当操作执行成功或失败时监听会自动触发。总之,所有的操作都会返回一个ChannelFuture。
-
ChannelInitializer:当一个链接建立时,我们需要知道怎么来接收或者发送数据,当然,我们有各种各样的Handler实现来处理它,那么ChannelInitializer便是用来配置这些Handler,它会提供一个ChannelPipeline,并把Handler加入到ChannelPipeline。
-
ChannelHandler:为了支持各种协议和处理数据的方式,便诞生了Handler组件。Handler主要用来处理各种事件,这里的事件很广泛,比如可以是连接、数据接收、异常、数据转换等。
三.inbound和outbound
我们的应用程序中用到的最多的应该就是ChannelHandler,我们可以这么想象,数据在一个ChannelPipeline中流动,而ChannelHandler便是其中的一个个的小阀门,这些数据都会经过每一个ChannelHandler并且被它处理。ChannelHandler有两个子类ChannelInboundHandler和ChannelOutboundHandler,如下图。这两个类对应了两个数据流向,如果数据是从外部流入我们的应用程序,我们就看做是inbound,相反便是outbound。事实上inbound和outbound是Netty自身根据事件在pipeline中的流向,抽象出来的术语。
我们知道InboundHandler和OutboundHandler在ChannelPipeline中是混合在一起的,那么它们如何区分彼此呢?其实很简单,因为它们各自实现的是不同的接口,对于inbound event,Netty会自动跳过OutboundHandler,相反若是outbound event,ChannelInboundHandler会被忽略掉。最典型的inbound和outbound就是Encoder和Decoder了。
还有要注意的是执行顺序,ChannelInboundHandler按照注册的先后顺序执行;ChannelOutboundHandler按照注册的先后顺序逆序执行,也就是说,可见, inbound 的顺序是跟 add 顺序一致的, 而 outbound 的顺序是跟 add 顺序相反的,如下图所示。
四.Netty的线程模型
Netty本质上遵循了经典的Reactor线程模型,通过调整线程池的线程个数、是否共享线程池等方式,Netty的Reactor线程模型可以在单线程、多线程和主从多线程间切换。
Netty是一个非阻塞的、事件驱动的、网络编程框架。我们很容易想到Netty会用线程来处理IO事件,对于熟悉多线程编程的人来说,你或许会想如何同步代码,但是Netty不需要我们考虑这些。在Reactor多线程模型中,一个NIO线程可同时处理N条链路,但一个链路只对应一个NIO线程,防止发生并发操作问题。Netty也是这样实现的,一个Channel会对应一个EventLoop,而一个EventLoop会对应着一个线程,也就是说,仅有一个线程在负责一个Channel的IO操作。当一个连接到达,Netty会注册一个channel,然后EventLoopGroup会分配一个EventLoop绑定到这个channel,在这个channel的整个生命周期过程中,都会由绑定的这个EventLoop来为它服务,而这个EventLoop就是一个线程。
创建ServerBootstrap时,要传递两个NioEventLoopGroup线程池,一个叫bossGroup,一个叫workGroup。bossGroup是用来处理TCP连接请求的,workGroup是来处理IO事件的。Netty3.0中workGroup的线程数默认是cpu数量的两倍,如果指定了线程数,netty会进行计算比较,使用两者中的较小值。而bossGroup默认线程数就是1。Netty5.0中这两者的线程数默认都是cpu数量的两倍。如果你的应用服务的QPS只是几百万,那么parentGroup只需要设置为2,childGroup设置为4,就足以应付,应用如果需要承受上千万上亿流量的,需要另外调整线程数。