nettynettynetty

Netty概述

2017-09-19  本文已影响769人  jiangmo

综述

netty通过Reactor模型基于多路复用器接收并处理用户请求(能讲就多讲一点),内部实现了两个线程池,boss线程池和work线程池,其中boss线程池的线程负责处理请求的accept事件,当接收到accept事件的请求时,把对应的socket封装到一个NioSocketChannel中,并交给work线程池,其中work线程池负责请求的read和write事件(通过口述加画图的方式,把请求的执行过程大概描述了一遍,时间有限,也不可能把所有的细节都说完,挑重点讲,挑记忆深刻的讲)

框架


netty线程模型采用“服务端监听线程”和“IO线程”分离的方式,与多线程Reactor模型类似。

抽象出NioEventLoop来表示一个不断循环执行处理任务的线程,每个NioEventLoop有一个selector,用于监听绑定在其上的socket链路

逻辑架构

Netty的特点

Netty的高性能表现在哪些方面?

(1)采用异步非阻塞的 I/O 类库,基于 Reactor 模式实现,解决了传统同
步阻塞 I/O 模式下一个服务端无法平滑地处理线性增长的客户端的问题。
(2)TCP 接收和发送缓冲区使用直接内存代替堆内存,避免了内存复制,
提升了 I/O 读取和写入的性能。
(3)支持通过内存池的方式循环利用 ByteBuf,避免了频繁创建和销毁
ByteBuf 带来的性能损耗。
(4)可配置的 I/O 线程数、TCP 参数等,为不同的用户场景提供定制化的
调优参数,满足不同的性能场景。
(5)采用环形数组缓冲区实现无锁化并发编程,代替传统的线程安全容器
或者锁。
(6)合理地使用线程安全容器、原子类等,提升系统的并发处理能力。
(7)关键资源的处理使用单线程串行化的方式,避免多线程并发访问带来
的锁竞争和额外的 CPU 资源消耗问题。
(8)通过引用计数器及时地申请释放不再被引用的对象,细粒度的内存管
理降低了 GC 的频率,减少了频繁 GC 带来的时延增大和 CPU 损耗。

对服务端:会定时清除闲置会话inactive(netty5)
对客户端:用来检测会话是否断开,是否重来,检测网络延迟,其中idleStateHandler类 用来检测会话状态


一个NioEventLoop聚合了一个多路复用器Selector,因此可以处理成百上千的客户端连接,Netty的处理策略是每当有一个新的客户端接入,则从NioEventLoop线程组中顺序获取一个可用的NioEventLoop,当到达数组上限之后,重新返回到0,通过这种方式,可以基本保证各个NioEventLoop的负载均衡。一个客户端连接只注册到一个NioEventLoop上,这样就避免了多个IO线程去并发操作它。

Netty的高效并发编程主要体现在如下几点:

  1. volatile的大量、正确使用;
  2. CAS和原子类的广泛使用;
  3. 线程安全容器的使用;
  4. 通过读写锁提升并发性能。

Netty除了使用reactor来提升性能,当然还有
1、零拷贝,IO性能优化
2、通信上的粘包拆包
3、同步的设计
4、高性能的序列

Netty的线程模型

Netty 的线程模型并不是一成不变的,它实际取决于用户的启动参数配置。
通过设置不同的启动参数,Netty 可以同时支持 Reactor 单线程模型、多线程模
型和主从 Reactor 多线层模型。

服务端启动的时候,创建了两个 NioEventLoopGroup,它们实际是两个独立
的 Reactor 线程池。一个用于接收客户端的 TCP 连接,另一个用于处理 I/O 相关
的读写操作,或者执行系统 Task、定时任务 Task 等。

NioEventLoop设计原理

TCP 粘包/拆包的原因及解决方法

了解哪几种序列化协议

如何选择序列化协议

Netty的零拷贝实现

串行化设计避免线程竞争

定时任务与时间轮算法

在Netty中,有很多功能依赖定时任务,比较典型的有两种:

NioEventLoop中的Thread线程按照时间轮中的步骤不断循环执行:
a)在时间片Tirck内执行selector.select()轮询监听IO事件;
b)处理监听到的就绪IO事件;
c)执行任务队列taskQueue/delayTaskQueue中的非IO任务。

一种比较常用的设计理念是在NioEventLoop中聚合JDK的定时任务线程池ScheduledExecutorService,通过它来执行定时任务。这样做单纯从性能角度看不是最优,原因有如下三点:

最早面临上述问题的是操作系统和协议栈,例如TCP协议栈,其可靠传输依赖超时重传机制,因此每个通过TCP传输的 packet 都需要一个 timer来调度 timeout 事件。这类超时可能是海量的,如果为每个超时都创建一个定时器,从性能和资源消耗角度看都是不合理的。

根据George Varghese和Tony Lauck 1996年的论文《Hashed and Hierarchical Timing Wheels: data structures to efficiently implement a timer facility》提出了一种定时轮的方式来管理和维护大量的timer调度。
Netty的定时任务调度就是基于时间轮算法调度,下面我们一起来看下Netty的实现。
定时轮是一种数据结构,其主体是一个循环列表,每个列表中包含一个称之为slot的结构,它的原理图如下:


定时轮的工作原理可以类比于时钟,如上图箭头(指针)按某一个方向按固定频率轮动,每一次跳动称为一个tick。这样可以看出定时轮由个3个重要的属性参数:ticksPerWheel(一轮的tick数),tickDuration(一个tick的持续时间)以及 timeUnit(时间单位),例如当ticksPerWheel=60,tickDuration=1,timeUnit=秒,这就和时钟的秒针走动完全类似了。
下面我们具体分析下Netty的实现:时间轮的执行由NioEventLoop来复杂检测,首先看任务队列中是否有超时的定时任务和普通任务,如果有则按照比例循环执行这些任务,代码如下:


如果没有需要立即执行的任务,则调用Selector的select方法进行等待,等待的时间为定时任务队列中第一个超时的定时任务时延,代码如下:

从定时任务Task队列中弹出delay最小的Task,计算超时时间;
定时任务的执行:经过周期tick之后,扫描定时任务列表,将超时的定时任务移除到普通任务队列中,等待执行,相关代码如下:

检测和拷贝任务完成之后,就执行超时的定时任务,代码如下:


为了保证定时任务的执行不会因为过度挤占IO事件的处理,Netty提供了IO执行比例供用户设置,用户可以设置分配给IO的执行比例,防止因为海量定时任务的执行导致IO处理超时或者积压。
因为获取系统的纳秒时间是件耗时的操作,所以Netty每执行64个定时任务检测一次是否达到执行的上限时间,达到则退出。如果没有执行完,放到下次Selector轮询时再处理,给IO事件的处理提供机会,代码如下:

聚焦而不是膨胀

Netty是个异步高性能的NIO框架,它并不是个业务运行容器,因此它不需要也不应该提供业务容器和业务线程。合理的设计模式是Netty只负责提供和管理NIO线程,其它的业务层线程模型由用户自己集成,Netty不应该提供此类功能,只要将分层划分清楚,就会更有利于用户集成和扩展。
令人遗憾的是在Netty 3系列版本中,Netty提供了类似Mina异步Filter的ExecutionHandler,它聚合了JDK的线程池java.util.concurrent.Executor,用户异步执行后续的Handler。
ExecutionHandler是为了解决部分用户Handler可能存在执行时间不确定而导致IO线程被意外阻塞或者挂住,从需求合理性角度分析这类需求本身是合理的,但是Netty提供该功能却并不合适。原因总结如下:

  1. 它打破了Netty坚持的串行化设计理念,在消息的接收和处理过程中发生了线程切换并引入新的线程池,打破了自身架构坚守的设计原则,实际是一种架构妥协;
  2. 潜在的线程并发安全问题,如果异步Handler也操作它前面的用户Handler,而用户Handler又没有进行线程安全保护,这就会导致隐蔽和致命的线程安全问题;
  3. 用户开发的复杂性,引入ExecutionHandler,打破了原来的ChannelPipeline串行执行模式,用户需要理解Netty底层的实现细节,关心线程安全等问题,这会导致得不偿失。
    鉴于上述原因,Netty的后续版本彻底删除了ExecutionHandler,而且也没有提供类似的相关功能类,把精力聚焦在Netty的IO线程NioEventLoop上,这无疑是一种巨大的进步,Netty重新开始聚焦在IO线程本身,而不是提供用户相关的业务线程模型。

Netty线程开发最佳实践

Ref:
http://www.jianshu.com/p/03bb8a945b37
http://www.voidcn.com/article/p-flamborw-bhh.html
http://www.voidcn.com/article/p-onnrusud-mz.html
http://blog.csdn.net/qq924862077/article/details/53316490

上一篇 下一篇

猜你喜欢

热点阅读