一文让你深刻了解 Java-Netty高性能高并发

一丶 Netty基础入门

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

做为当前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通讯行业等得到了普遍的应用,一些业界著名的开源组件也基于Netty的NIO框架构建。程序员

二丶 Netty高性能之道

RPC调用的性能模型分析算法

RPC 的全称是 Remote Procedure Call 是一种进程间通讯方式。 它容许程序调用另外一个地址空间(一般是共享网络的另外一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员不管是调用本地的仍是远程的函数,本质上编写的调用代码基本相同。编程

咱们追溯下当初开发 RPC 的原动机是什么?在 Nelson 的论文 Implementing Remote Procedure Calls(参考[2]) 中他提到了几点:安全

  • 简单:RPC 概念的语义十分清晰和简单,这样创建分布式计算就更容易。性能优化

  • 高效:过程调用看起来十分简单并且高效。服务器

  • 通用:在单机计算中「过程」每每是不一样算法部分间最重要的通讯机制。

通俗一点说,就是通常程序员对于本地的过程调用很熟悉,那么咱们把 RPC 作成和本地调用彻底相似,那么就更容易被接受,使用起来毫无障碍。 Nelson 的论文发表于 30 年前,其观点今天看来确实高瞻远瞩,今天咱们使用的 RPC 框架基本就是按这个目标来实现的。网络

传统RPC调用性能差的三大误区多线程

网络传输方式问题:传统的RPC框架或者基于RMI等方式的远程服务(过程)调用采用了同步阻塞IO,当客户端的并发压力或者网络时延增大以后,同步阻塞IO会因为频繁的wait致使IO线程常常性的阻塞,因为线程没法高效的工做,IO处理能力天然降低。架构

下面,咱们经过BIO通讯模型图看下BIO通讯的弊端:

一文让你深刻了解 Java-Netty高性能高并发

采用BIO通讯模型的服务端,一般由一个独立的Acceptor线程负责监听客户端的链接,接收到客户端链接以后为客户端链接建立一个新的线程处理请求消息,处理完成以后,返回应答消息给客户端,线程销毁,这就是典型的一请求一应答模型。该架构最大的问题就是不具有弹性伸缩能力,当并发访问量增长后,服务端的线程个数和并发访问数成线性正比,因为线程是JAVA虚拟机很是宝贵的系统资源,当线程数膨胀以后,系统的性能急剧降低,随着并发量的继续增长,可能会发生句柄溢出、线程堆栈溢出等问题,并致使服务器最终宕机。

高性能的三大要素

  1. 传输:用什么样的通道将数据发送给对方,BIO、NIO或者AIO,IO模型在很大程度上决定了框架的性能。

  2. 协议:采用什么样的通讯协议,HTTP或者内部私有协议。协议的选择不一样,性能模型也不一样。相比于公有协议,内部私有协议的性能一般能够被设计的更优。

  3. 线程:数据报如何读取?读取以后的编解码在哪一个线程进行,编解码后的消息如何派发,Reactor线程模型的不一样,对性能的影响也很是大。

异步非阻塞通讯

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

一文让你深刻了解 Java-Netty高性能高并发

与Socket类和ServerSocket类相对应,NIO也提供了SocketChannel和ServerSocketChannel两种不一样的套接字通道实现。这两种新增的通道都支持阻塞和非阻塞两种模式。阻塞模式使用很是简单,可是性能和可靠性都很差,非阻塞模式正好相反。开发人员通常能够根据本身的须要来选择合适的模式,通常来讲,低负载、低并发的应用程序能够选择同步阻塞IO以下降编程复杂度。可是对于高负载、高并发的网络应用,须要使用NIO的非阻塞模式进行开发。

零拷贝

零拷贝是Netty的重要特性之一,而究竟什么是零拷贝呢?

"Zero-copy" describes computer operations in which the CPU does not perform the task of copying data from one memory area to another.

从WIKI的定义中,咱们看到“零拷贝”是指计算机操做的过程当中,CPU不须要为数据在内存之间的拷贝消耗资源。而它一般是指计算机在网络上发送文件时,不须要将文件内容拷贝到用户空间(User Space)而直接在内核空间(Kernel Space)中传输到网络的方式。

Non-Zero Copy方式:

一文让你深刻了解 Java-Netty高性能高并发

Non-Zero Copy方式

一文让你深刻了解 Java-Netty高性能高并发

Zero Copy方式:

从上图中能够清楚的看到,Zero Copy的模式中,避免了数据在用户空间和内存空间之间的拷贝,从而提升了系统的总体性能。Linux中的sendfile()以及Java NIO中的FileChannel.transferTo()方法都实现了零拷贝的功能,而在Netty中也经过在FileRegion中包装了NIO的FileChannel.transferTo()方法实现了零拷贝。

而在Netty中还有另外一种形式的零拷贝,即Netty容许咱们将多段数据合并为一整段虚拟数据供用户使用,而过程当中不须要对数据进行拷贝操做,这也是咱们今天要讲的重点。咱们都知道在stream-based transport(如TCP/IP)的传输过程当中,数据包有可能会被从新封装在不一样的数据包中,例如当你发送以下数据时:

一文让你深刻了解 Java-Netty高性能高并发

有可能实际收到的数据以下:

一文让你深刻了解 Java-Netty高性能高并发

所以在实际应用中,颇有可能一条完整的消息被分割为多个数据包进行网络传输,而单个的数据包对你而言是没有意义的,只有当这些数据包组成一条完整的消息时你才能作出正确的处理,而Netty能够经过零拷贝的方式将这些数据包组合成一条完整的消息供你来使用。而此时,零拷贝的做用范围仅在用户空间中。

内存池

为何要使用内存池?

随着JVM虚拟机和JIT即时编译技术的发展,对象的分配和回收是个很是轻量级的工做。可是对于缓冲区Buffer,状况却稍有不一样,特别是对于堆外直接内存的分配和回收,是一件耗时的操做。并且这些实例随着消息的处理朝生夕灭,这就会给服务器带来沉重的GC压力,同时消耗大量的内存。为了尽可能重用缓冲区,Netty提供了基于内存池的缓冲区重用机制。性能测试代表,采用内存池的ByteBuf相比于朝生夕灭的ByteBuf,性能高23倍左右(性能数据与使用场景强相关)。

如何启动并初始化内存池?

在Netty4或Netty5中实现了一个新的ByteBuf内存池,它是一个纯Java版本的 jemalloc (Facebook也在用)。如今,Netty不会再由于用零填充缓冲区而浪费内存带宽了。 不过,因为它不依赖于GC,开发人员须要当心内存泄漏。若是忘记在处理程序中释放缓冲区,那么内存使用率会无限地增加。 Netty默认不使用内存池,须要在建立客户端或者服务端的时候在引导辅助类中进行配置:

一文让你深刻了解 Java-Netty高性能高并发

如何在本身的业务代码中使用内存池?

首先,介绍一下Netty的ByteBuf缓冲区的种类:ByteBuf支持堆缓冲区和堆外直接缓冲区,根据经验来讲,底层IO处理线程的缓冲区使用堆外直接缓冲区,减小一次IO复制。业务消息的编解码使用堆缓冲区,分配效率更高,并且不涉及到内核缓冲区的复制问题。

ByteBuf的堆缓冲区又分为内存池缓冲区PooledByteBuf和普通内存缓冲区UnpooledHeapByteBuf。PooledByteBuf采用二叉树来实现一个内存池,集中管理内存的分配和释放,不用每次使用都新建一个缓冲区对象。UnpooledHeapByteBuf每次都会新建一个缓冲区对象。在高并发的状况下推荐使用PooledByteBuf,能够节约内存的分配。在性能可以保证的状况下,可使用UnpooledHeapByteBuf,实现比较简单。

在此说明这是当咱们在业务代码中要使用池化的ByteBuf时的方法:

第一种状况:若咱们的业务代码只是为了将数据写入ByteBuf中并发送出去,那么咱们应该使用堆外直接缓冲区DirectBuffer.使用方式以下:

一文让你深刻了解 Java-Netty高性能高并发

高效的Reactor线程模型

Reactor模式是事件驱动的,有一个或多个并发输入源,有一个Service Handler,有多个Request Handlers;这个Service Handler会同步的将输入的请求(Event)多路复用的分发给相应的Request Handler

一文让你深刻了解 Java-Netty高性能高并发

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

这个作的好处有不少,首先咱们能够将处理event的Request handler实现一个单独的线程,即:

一文让你深刻了解 Java-Netty高性能高并发

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

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

一文让你深刻了解 Java-Netty高性能高并发

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

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

Reactor多线程模型

一文让你深刻了解 Java-Netty高性能高并发

这样经过Reactor Thread Pool来提升event的分发能力

Reactor主从模型

一文让你深刻了解 Java-Netty高性能高并发

Netty的高效并发编程主要体如今以下几点

1) volatile的大量、正确使用;
2) CAS和原子类的普遍使用;
3) 线程安全容器的使用;
4) 经过读写锁提高并发性能。

Netty除了使用reactor来提高性能,固然还有

一、零拷贝,IO性能优化
二、通讯上的粘包拆包
三、同步的设计
四、高性能的序列

写在最后:

欢迎你们关注我新开通的公众号【风平浪静如码】,海量Java相关文章,学习资料都会在里面更新,整理的资料也会放在里面。

以为写的还不错的就点个赞,加个关注呗!点关注,不迷路,持续更新!!!