Netty的常用组件及线程模型设计(一)
Netty常用组件
Bootstrap
Bootstrap是Netty框架的启动类和主入口类,发呢为客户端类Bootstrap和服务器类ServerBootstrap两种
Channel
- Channel是JavaNIO的一个基本构造,它代表一个到实体(如一个硬件设备、一个文件、一个网络套接字或者一个能够执行一个或者多个不同的IO操作的程序组件)的开发连接,如读操作和写操作
- 目前,可以把Channel看作是传入(入站)或者传出(出站)数据的载体。因此,它可以被打开或者被关闭,连接或者断开连接
EventLoop/EventLoopGroup(重点理解)
-
EventLoop可以看成一个线程,EventLoopGroup自然就可以看成线程组
在NIO中一个while循环select出事件,然后依次处理每种事件,我们可以把它称为事件循环,
这就是EventLoop,它定义了Netty的核心抽象,用于处理网络连接的声明周期中所发生的事件。
io.netty.util.concurrent包构建在JDK的java.util.concurrent包上。而io.netty.channel包中的类,
为了与Channel的事件进行交互,扩展了这些接口/类,一个EventLoop将由一个永远都不会改变的Thread驱动,同时任务(Runnable/Callable)可以直接交给EventLoop实现,以立即执行或者调度执行 -
EventLoop继承关系图
-
线程的分配.
服务于Channel的I/O和事件的EventLoop包含在EventLoopGroup中,异步传输实现只使用了少量的EventLoop(以及和它们相关联的Thread),而且在当前的线程模型中,它们可能会被多个Channel所共享,这使得可以通过尽可能少的Thread来支撑大量的Channel,而不是每个Channel分配一个Thread.EventLoopGroup负责为每个新创建的Channel分配一个EventLoop.在当前实现中,使用顺序循环(round-robin)的方式进行分配以获取一个均衡的分布,并且相同的EventLoop可能会被分配给多个Channel.一旦一个Channel被分配给一个EventLoop,它将在它的整个声明周期中都使用这个EventLoop(以及相关联的Thread).需要注意:EventLoop的分配方式对ThreadLocal的使用的影响。因为一个EventLoop通常会被用于支撑多个Channel,所以读与所有相关联的Channel来说,ThreadLocal都将是一样的。这使得它对于实现状态追踪等功能来说是个糟糕的选择。然而,在一些无状态的上下文中,它仍然可以被用于在多个Channel之间共享一些重度的或者代价昂贵的对象,甚至是事件
-
线程管理。
在内部,当提交任务到如果(当前)调用线程正是支撑EventLoop的线程,那么所提交的代码块将会被(直接)执行,否则,EventLoop将调度该任务以便稍后执行,并将它放入到内部队列中。当EventLoop下次处理它的事件时,它会执行队列中的那些任务/事件
-
为什么要设计成一个Channel中的事件处理只能交给一个EventLoop?
在以前的版本中所使用的线程模型只保证了入站(之前称为上游)事件会在所谓的IO线程(对应Netty4中的EventLoop)中执行。所有的出站(下游)事件都由调用线程处理,其可能是I/O线程也可能是别的线程。开始看起来这似乎是个好主意,但是已经被发现是有问题的,因为需要在ChannelHandler中对出站事件进行仔细的同步。简而言之,不可能保证多个线程在同一时刻尝试访问出站事件。例如你通过在不同的线程中调用Channel.write()方法,针对同一个Channel同时触发出站的事件,就会发生这种情况。当出站事件触发了入站事件时,将会导致另一个负面影响。当Channel.write()方法导致异常时,需要在调用线程中执行代码,然后将事件移交给I/O线程去执行(在出站事件中,因为出站的数据是要发往对端的,所以当异常触发时要把它当作入站,交由发送端来处理)然而这将带来额外的上下文切换。
Netty4中所采用的线程模型,通过在同一个线程中处理某个给定的EventLoop中所产生的所有事件,解决了这个问题,这提供了一个更加简单的执行体系架构,并且消除了在多个ChannelHandler中进行同步的需要,也解决了线程安全和同步的问题
事件
- Netty使用不同的事件来通知我们状态的改变或者是操作的状态。这使得我们能够基于已经发生的事件来触发适当的动作
- Netty事件是按照它们与入站或出站数据流的相关性进行分类的。可能由入站数据或者相关的状态更改而触发的事件包括:连接已被激活或者连接失活;数据读取;用户事件;错误事件;
- 出站事件是未来将会触发的某个动作的操作结果,这些动作包括:打开或者关闭到远程节点的连接;将数据写到或者冲刷到套接字
ChannelHandler
- 每个事件都可以被分发给ChannelHandler类中的某个用户实现的方法,既然事件分为入站和出站,用来处理事件的ChannelHandler也被分为,可以处理入站事件的Handler和出站事件的Handler,当然有些Handler,既可以处理入站也可以处理出站
- Netty提供提供了大量预定义的可以开箱即用的ChannelHandler实现,包括用于各种协议(如HTTP和SSL/TLS)的ChannelHandler
- ChannelInboundHandler入站处理,它是一个接口,其中使用最多的是SimpleChannelInboundHandler,因为它会帮我们自动释放Bytebuf,如果需要手动释放也可以实现其他入站处理器,如ChannelInboundHandlerAdapter。ChannelOutboundHandler类型的对应ChannelOutboundHandlerAdapter
ChannelPipeline
- 基于Netty的网路应用程序中根据业务需求会使用Netty已经提供的Channelhandler
或者自行开发ChannelHandler,这些ChannelHandler都放在ChannelPipeline中统一
管理,事件就会在ChannelPipeline中流动,并被其中一个或者多个ChannelHandler处理
ChannelFuture
- Netty中所有的IO操作都是异步的,我们知道"异步的意思就是不需要主动等待结果的返回,
而是通过其他手段比如,状态通知,回调函数等",也就是说至少我们需要一种获得异步执行
结果的手段 - JDK预置了java.util.concurrent.Future,Future提供了一种在操作完成时通知应用程序的方式,
这个对象可以看作是一个异步操作的结果的占位符;它将在未来的某个时刻完成,并提供对其
结果的访问。但是其所提供的实现,只允许手动检查对应的操作是否已经完成,或者一直阻塞
直到它完成。这是非常繁琐的,所以Netty提供了它自己的实现ChannelFture,用于在执行异步
操作的时候使用,一般来说,每个Netty的出站I/O操作都将返回一个ChannelFuture。
我们还可以添加ChannelFutureListener,针对某个异步操作添加通知回调
- JDK原生Future提供的方法
优势
- 1.API使用简单,开发门槛低
- 2.功能强大,预置了多种编解码功能,支持多种主流协议
- 3.定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展
- 4.性能高,通过与其他业界主流地NIO框架对比,Netty的综合性能最优
- 5.成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务人员不需要再为NIO的bug而烦恼
- 6.社区活跃,版本迭代周期短,发现的bug可以被及时修复,同事,更多的新功能会加入
- 7.经历了大规模的商业应用考研,直到得到验证