Netty源码分析之Reactor线程模型源码分析

  • Netty源码分析之Reactor线程模型源码分析已关闭评论
  • 11 views
  • A+
所属分类:编程开发

多线程

随着硬件性能的提升,CPU的核数越来越越多,很多服务器标配已经达到32或64核。通过多线程并发编程,可以充分利用多核CPU的处理能力,提升系统的处理效率和并发性能。

从2005年开始,随着多核处理器的逐步普及,java的多线程并发编程也逐渐流行起来,当时商用主流的JDK版本是1.4,用户可以通过 new Thread()的方式创建新的线程。

由于JDK1.4并没有提供类似线程池这样的线程管理容器,多线程之间的同步、协作、创建和销毁等工作都需要用户自己实现。由于创建和销毁线程是个相对比较重量级的操作,因此,这种原始的多线程编程效率和性能都不高。

线程池

为了提升Java多线程编程的效率和性能,降低用户开发难度。JDK1.5推出了java.util.concurrent并发编程包。在并发编程类库中,提供了线程池、线程安全容器、原子类等新的类库,极大的提升了Java多线程编程的效率,降低了开发难度。

从JDK1.5开始,基于线程池的并发编程已经成为Java多核编程的主流。

线程模型

Reactor线程模型,指的是所有的IO操作都在同一个NIO线程上面完成,NIO线程的职责如下:

1)作为NIO服务端,接收客户端的TCP连接;

2)作为NIO客户端,向服务端发起TCP连接;

3)读取通信对端的请求或者应答消息;

4)向通信对端发送消息请求或者应答消息。

Reactor单线程模型示意图如下所示:

Netty源码分析之Reactor线程模型源码分析

Reactor单线程模型

由于Reactor模式使用的是异步非阻塞IO,所有的IO操作都不会导致阻塞,理论上一个线程可以独立处理所有IO相关的操作。从架构层面看,一个NIO线程确实可以完成其承担的职责。例如,通过Acceptor类接收客户端的TCP连接请求消息,链路建立成功之后,通过Dispatch将对应的ByteBuffer派发到指定的Handler上进行消息解码。用户线程可以通过消息编码通过NIO线程将消息发送给客户端。

对于一些小容量应用场景,可以使用单线程模型。但是对于高负载、大并发的应用场景却不合适,主要原因如下:

1)一个NIO线程同时处理成百上千的链路,性能上无法支撑,即便NIO线程的CPU负荷达到100%,也无法满足海量消息的编码、解码、读取和发送;

2)当NIO线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重了NIO线程的负载,最终会导致大量消息积压和处理超时,成为系统的性能瓶颈;

3)可靠性问题:一旦NIO线程意外跑飞,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障。

为了解决这些问题,演进出了Reactor多线程模型,下面我们一起学习下Reactor多线程模型。

多线程模型

Rector多线程模型与单线程模型最大的区别就是有一组NIO线程处理IO操作,它的原理图如下:

Netty源码分析之Reactor线程模型源码分析

Reactor多线程模型

Reactor多线程模型的特点:

1)有专门一个NIO线程-Acceptor线程用于监听服务端,接收客户端的TCP连接请求;

2)网络IO操作-读、写等由一个NIO线程池负责,线程池可以采用标准的JDK线程池实现,它包含一个任务队列和N个可用的线程,由这些NIO线程负责消息的读取、解码、编码和发送;

3)1个NIO线程可以同时处理N条链路,但是1个链路只对应1个NIO线程,防止发生并发操作问题。

在绝大多数场景下,Reactor多线程模型都可以满足性能需求;但是,在极个别特殊场景中,一个NIO线程负责监听和处理所有的客户端连接可能会存在性能问题。例如并发百万客户端连接,或者服务端需要对客户端握手进行安全认证,但是认证本身非常损耗性能。在这类场景下,单独一个Acceptor线程可能会存在性能不足问题,为了解决性能问题,产生了第三种Reactor线程模型-主从Reactor多线程模型。

Netty线程模型

Netty是一个高性能、异步事件驱动的NIO框架,它提供了对TCP、UDP和文件传输的支持,作为一个异步NIO框架,Netty的所有IO操作都是异步非阻塞的,通过Future-Listener机制,用户可以方便的主动获取或者通过通知机制获得IO操作结果。

作为当前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,一些业界著名的开源组件也基于Netty的NIO框架构建

传统通信采用了同步阻塞IO,当客户端的并发压力或者网络时延增大之后,同步阻塞IO会由于频繁的wait导致IO线程经常性的阻塞,由于线程无法高效的工作,IO处理能力自然下降。

我们通过BIO通信模型图看下BIO通信的弊端

采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,接收到客户端连接之后为客户端连接创建一个新的线程处理请求消息,处理完成之后,返回应答消息给客户端,线程销毁,这就是典型的一请求一应答模型。

该架构最大的问题就是不具备弹性伸缩能力,当并发访问量增加后,服务端的线程个数和并发访问数成线性正比,由于线程是JAVA虚拟机非常宝贵的系统资源,当线程数膨胀之后,系统的性能急剧下降,随着并发量的继续增加,可能会发生句柄溢出、线程堆栈溢出等问题,并导致服务器最终宕机。

Netty基于NIO,实现了对NIO的封装及优化,从而Netty的通信模式为异步非阻塞通信

在IO编程过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者IO多路复用技术进行处理。IO多路复用技术通过把多个IO的阻塞复用到同一个select的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程/多进程模型比,I/O多路复用的最大优势是系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程的运行,降低了系统的维护工作量,节省了系统资源。

JDK1.4提供了对非阻塞IO(NIO)的支持,JDK1.6版本使用epoll替代了传统的select/poll,极大的提升了NIO通信的性能

Netty架构按照Reactor模式设计和实现,它的服务端通信序列图如下

Netty的IO线程NioEventLoop由于聚合了多路复用器Selector,可以同时并发处理成百上千个客户端Channel,由于读写操作都是非阻塞的,这就可以充分提升IO线程的运行效率,避免由于频繁IO阻塞导致的线程挂起。另外,由于Netty采用了异步通信模式,一个IO线程可以并发处理N个客户端连接和读写操作,这从根本上解决了传统同步阻塞IO一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。

何为Reactor线程模型?

从结构上,这有点类似生产者消费者模式,即有一个或多个生产者将事件放入一个Queue中,而一个或多个消费者主动的从这个Queue中Poll事件来处理;而Reactor模式则并没有Queue来做缓冲,每当一个Event输入到Service Handler之后,该Service Handler会立刻的根据不同的Event类型将其分发给对应的Request Handler来处理。

这个做的好处有很多,首先我们可以将处理event的Request handler实现一个单独的线程,即

这样Service Handler 和request Handler实现了异步,加快了service Handler处理event的速度,那么每一个request同样也可以以多线程的形式来处理自己的event,即Thread1 扩展成Thread pool 1,

Netty的Reactor线程模型1 Reactor单线程模型 Reactor机制中保证每次读写能非阻塞读写

一个线程(单线程)来处理CONNECT事件(Acceptor),一个线程池(多线程)来处理read,一个线程池(多线程)来处理write,那么从Reactor Thread到handler都是异步的,从而IO操作也多线程化。

到这里跟BIO对比已经提升了很大的性能,但是还可以继续提升,由于Reactor Thread依然为单线程,从性能上考虑依然有所限制

2 Reactor多线程模型

这样通过Reactor Thread Pool来提高event的分发能力

3 Reactor主从模型

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

1) volatile的大量、正确使用;

2) CAS和原子类的广泛使用;

3) 线程安全容器的使用;

4) 通过读写锁提升并发性能。

Netty除了使用reactor来提升性能,当然还有

1、零拷贝,IO性能优化

2、通信上的粘包拆包

2、同步的设计

3、高性能的序列

  • 我的微信
  • 微信扫一扫
  • weinxin
  • 微信公众号
  • 微信公众号扫一扫
  • weinxin
avatar