并发编程:能够充分利用多核CPU的处理能力,提高系统的处理效率和并发性能。react
基于事件驱动(异步非阻塞IO),特别适合处理海量的I/O事件。算法
全部的IO操做都在同一个NIO线程上面完成。数据库
缺点:编程
a、一个NIO线程同时处理成百上千的链路,性能上没法支撑,即使NIO线程的CPU负荷达到100%,也没法知足海量消息的编码、解码、读取和发送;后端
b、当NIO线程负载太重以后,处理速度将变慢,这会致使大量客户端链接超时,超时以后每每会进行重发,这更加剧了NIO线程的负载,最终会致使大量消息积压和处理超时,成为系统的性能瓶颈;设计模式
有专门一个NIO线程-Acceptor线程用于监听服务端,接收客户端的TCP链接请求,网络IO操做-读、写等由一个NIO线程池负责,线程池能够采用标准的JDK线程池实现,它包含一个任务队列和N个可用的线程,由这些NIO线程负责消息的读取、解码、编码和发送,1个NIO线程能够同时处理N条链路,可是1个链路只对应1个NIO线程,防止发生并发操做问题。数组
缺点:缓存
a、一个NIO线程负责监听和处理全部的客户端链接可能会存在性能问题。例如并发百万客户端链接,或者服务端须要对客户端握手进行安全认证,可是认证自己很是损耗性能。安全
服务端用于接收客户端链接的再也不是个1个单独的NIO线程,而是一个独立的NIO线程池。Acceptor接收到客户端TCP链接请求处理完成后(可能包含接入认证等),将新建立的SocketChannel注册到IO线程池(sub reactor线程池)的某个IO线程上,由它负责SocketChannel的读写和编解码工做。Acceptor线程池仅仅只用于客户端的登录、握手和安全认证,一旦链路创建成功,就将链路注册到后端subReactor线程池的IO线程上,由IO线程负责后续的IO操做。性能优化
避免线程上下文切换和数据并发控制。
优势:
a、从消息的读取、编码以及后续Handler的执行,始终都由IO线程NioEventLoop负责,这就意外着整个流程不会进行线程上下文的切换,数据也不会面临被并发修改的风险。
b、一个NioEventLoop聚合了一个多路复用器Selector,所以能够处理成百上千的客户端链接,Netty的处理策略是每当有一个新的客户端接入,则从NioEventLoop线程组中顺序获取一个可用的NioEventLoop,当到达数组上限以后,从新返回到0,经过这种方式,能够基本保证各个NioEventLoop的负载均衡。一个客户端链接只注册到一个NioEventLoop上,这样就避免了多个IO线程去并发操做它。
总结:Netty经过串行化设计理念下降了用户的开发难度,提高了处理性能。利用线程组实现了多个串行化线程水平并行执行,线程之间并无交集,这样既能够充分利用多核提高并行处理能力,同时避免了线程上下文的切换和并发保护带来的额外性能损耗。
场景:
一、客户端链接超时控制;
二、链路空闲检测。
一种比较经常使用的设计理念是在NioEventLoop中聚合JDK的定时任务线程池ScheduledExecutorService,经过它来执行定时任务。这样作单纯从性能角度看不是最优,缘由有以下三点:
a、在IO线程中聚合了一个独立的定时任务线程池,这样在处理过程当中会存在线程上下文切换问题,这就打破了Netty的串行化设计理念;
b、存在多线程并发操做问题,由于定时任务Task和IO线程NioEventLoop可能同时访问并修改同一份数据;
c、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的实现。
Netty是个异步高性能的NIO框架,它并非个业务运行容器,所以它不须要也不该该提供业务容器和业务线程。合理的设计模式是Netty只负责提供和管理NIO线程,其它的业务层线程模型由用户本身集成,Netty不该该提供此类功能,只要将分层划分清楚,就会更有利于用户集成和扩展。
一、时间可控的简单业务直接在IO线程上处理
二、复杂和时间不可控业务建议投递到后端业务线程池统一处理
后端的业务线程池处理各类类型的业务消息,有些是I/O密集型、有些是CPU密集型、有些是纯内存计算型,不一样的业务处理时延,以及发生故障的几率都是不一样的。若是把业务线程和I/O线程合并,就会存在以下问题:
1.一、某类业务处理较慢,阻塞I/O线程,致使其它处理较快的业务消息的响应没法及时发送出去。
1.二、即使是同类业务,若是使用同一个I/O线程同时处理业务逻辑和I/O读写,若是请求消息的业务逻辑处理较慢,一样会致使响应消息没法及时发送出去。
I/O线程和业务线程分离以后,双方职责单一,有利于代码维护和问题定位。若是合设在一块儿,当RPC调用时延增大以后,究竟是网络问题、仍是I/O线程问题、仍是业务逻辑问题致使的时延大,纠缠在一块儿,问题定位难度很是大。例如业务线程中访问缓存或者数据库偶尔时延增大,就会致使I/O线程被阻塞,时延出现毛刺,这些时延毛刺的定位,难度很是大。
NioEventLoopGroup的建立并非廉价的,它会聚合Selector,Selector自己就会消耗句柄资源。
Netty的NioEventLoop设计理念就是经过有限的I/O线程,经过多路复用和非阻塞的方式,一个线程同时处理成百上千个链路,来解决传统一链接一线程的同步阻塞模型。所以,它的建立成本也较高,一个进程中不宜建立过多NioEventLoop。线程切换的代价:若是不是追求极致的性能,线程切换只要不过于频繁,它的代价仍是能够接受的。在一个复杂的系统中,当你集成第三方SDK时,例如Redis Client,一般都包含着隐式的线程切换。
缺点:
a、性能问题:JDK线程池默认采用一个阻塞队列,N个work线程的模式,随着work线程数的增长、队列的争用会很是激烈,进而致使性能降低。
建议采用N组线程池,每一个线程池线程数尽可能少的方式增长并行处理能力,减小锁争用。
b、故障隔离问题:若是后端只有一个线程池,某个服务故障将会致使整个进程不可用。采用分组处理业务服务的方式,能够下降故障的影响范围
Ref:
https://mp.weixin.qq.com/s/saKZIqugR_2KZYmSQMPPog