一.概述

 

 1.1惊人的性能数据

 

 最近,圈子里的一个朋友私信告诉我,他们通过使用Netty4+节俭压缩二进制编解码技术,实现了10W TPS(1K复杂POJO对象)的跨节点远程服务调用。与传统的基于Java序列化+同步阻塞IO的通信框架相比,性能提高了8倍以上。

 

 事实上,我对这些数据并不感到惊讶。根据我5年多的NIO编程经验,通过选择合适的NIO框架,添加高性能压缩二进制编解码技术,并精心设计反应器线程模型,完全有可能达到上述性能指标。

 

 让我们看看Netty如何支持10W TPS的跨节点远程服务调用。在正式解释之前,让我们简单介绍一下内蒂。

 1.2网络基础知识介绍

 

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

 

 作为最流行的NIO框架,Netty已经广泛应用于互联网、大数据分布式计算、游戏行业、通信行业等。业内一些著名的开源组件也是基于Netty的NIO框架构建的。

 二、RPC调用的性能模型分析

 

 2.1传统RPC调用性能差的三大罪过

 

 1网络传输模式问题

 

 传统的远程过程控制框架或基于远程信息的远程服务(过程)调用等。采用同步阻塞输入输出。当客户端或网络延迟的并发压力增加时,同步阻塞IO将由于频繁等待而导致IO线程频繁阻塞。因为线程无法有效工作,所以IO处理能力自然会下降。

 

 下面,我们通过BIO通信模型图来看一下BIO通信的缺点:

 NIO框架详细说明:内蒂的高性能道路_1.png

 

 在使用BIO通信模型的服务器端,独立的接收器线程通常负责监控客户端连接。接收到客户端连接后,创建一个新线程来处理客户端连接的请求消息。处理完成后,响应消息被返回给客户端,线程被销毁。这是典型的一个请求一个响应模型。这种架构最大的问题是它没有弹性的可伸缩性。当并发访问的数量增加时,服务器上的线程数量与并发访问的数量成线性比例。由于线程是JAVA虚拟机的宝贵系统资源,当线程数量增加时,系统性能急剧下降。随着并发性的不断增加,可能会出现句柄溢出和线程堆栈溢出等问题,这最终会导致服务器停机。

 

 2序列化模式问题

 

 在Java序列化中有几个典型的问题:

 

 Java序列化机制是Java中的一种对象编码和解码技术,不能跨语言使用;例如,对于异构系统之间的对接,需要通过其他语言将Java序列化的码流反序列化为原始对象(副本),这是目前难以支持的;

 与其他开源序列化框架相比,Java序列化后的代码流太大,无论是通过网络传输还是保存到磁盘,都会导致额外的资源占用;

 序列化性能很差(CPU资源很高)。

 

 3线程模型问题

 

 由于IO的同步阻塞,每个TCP连接将占用一个线程。由于线程资源是JVM虚拟机非常宝贵的资源,当IO读写阻塞导致线程被及时释放时,会导致系统性能急剧下降,甚至导致虚拟机无法创建新的线程。

 2.2高性能的三个主题

 

 1)传输:

 使用什么样的通道来相互发送数据,如生物信息、NIO或AIO,IO模型在很大程度上决定了框架的性能。

 

 2)协议:

 使用哪种通信协议,HTTP还是内部私有协议。不同的协议有不同的性能模型。与公共协议相比,内部私有协议的性能通常可以设计得更好。

 

 3)螺纹:

 如何读取数据报?反应器的不同线程模型对性能有很大的影响,例如读取后编码和解码哪个线程,如何分配编码和解码的消息。

 

 RPC调用性能的三个要素:

 NIO框架详细说明:内蒂的高性能道路_2.png

 第三,内蒂高性能的详细说明

 3.1异步无阻塞通信

 

 在IO编程过程中,当需要同时处理多个客户端访问请求时,可以使用多线程或IO复用技术进行处理。IO复用技术通过将多个IO的阻塞复用为同一个选择的阻塞,使系统在单线程的情况下同时处理多个客户端请求。与传统的多线程/多进程模型相比,输入/输出复用的最大优势在于系统不需要创建新的额外进程或线程,也不需要维护这些进程和线程的运行,从而减少了系统的维护工作量,节约了系统资源。

 

 JDK1.4提供对非阻塞IO的支持。在JDK1.5_update10中,使用epoll代替传统的选择/轮询,大大提高了NIO的通信性能。

 

 JDK NIO通信模型如下:

 NIO框架详细说明:内蒂的高性能道路_3.png

 

 与Socket类和ServerSocket类相对应,NIO还提供了两种不同的SocketChannel实现:SocketChannel和ServerSocketChannel。这两个新通道支持阻塞和非阻塞模式。阻塞模式使用起来非常简单,但是它的性能和可靠性并不好。非阻塞模式正好相反。开发人员一般可以根据自己的需要选择合适的模式。一般来说,低负载和低并发的应用程序可以选择同步阻塞输入输出,以降低编程复杂性。然而,对于具有高负载和高并发性的网络应用程序,应该使用NIO的非阻塞模式进行开发。

 

 网状架构是根据反应器模式设计和实现的,其服务器端通信序列图如下:

 NIO框架详细说明:内蒂的高性能道路_4.png

 

 客户端通信序列图如下:

 NIO框架详细说明:内蒂的高性能道路_5.png

 

 Netty的IO线程NioEventLoop可以同时处理数百个客户端通道,因为多路选择器的聚合。由于读写操作是非阻塞的,因此可以充分提高IO线程的运行效率,避免频繁的IO阻塞导致的线程挂起。另外,由于Netty采用异步通信模式,一个IO线程可以同时处理N个客户端连接和读写操作,从根本上解决了传统的同步阻塞IO连接线程模型,大大提高了体系结构的性能、灵活性和可靠性。

 3.2零拷贝

 

 许多用户听说过Netty的“零拷贝”功能,但不清楚它体现在哪里。本节将详细解释Netty的“零拷贝”功能。

 

 内蒂的“零拷贝”主要体现在以下三个方面:

 Netty的接收和发送字节缓冲区采用直接缓冲区,堆外的直接内存用于Socket读写,而不需要字节缓冲区的二次复制。如果传统的堆缓冲区用于套接字读取和写入,JVM会将堆缓冲区的副本复制到直接内存中,然后将其写入套接字。与堆外的直接内存相比,消息在发送过程中还有一个缓冲区的内存副本。

 Netty提供了一个组合缓冲区对象,它可以聚合多个字节缓冲区对象。用户可以像操作一个缓冲区一样轻松地操作组合缓冲区,避免了通过内存复制将几个小缓冲区合并成一个大缓冲区的传统方式。

 Netty的文件传输采用transferTo方法,可以将文件缓冲区中的数据直接发送到目标通道,避免了传统循环写方法带来的内存拷贝问题。

 

 接下来,我们将解释上述三种“零拷贝”,首先看一下Netty接收缓冲区的创建(异步消息读取“零拷贝”):

 NIO框架详细说明:内蒂的高性能道路_6.png

 

 每次在循环中读取消息时,通过ByteBufAllocator的ioBuffer方法获得ByteBuf对象。让我们继续看看它的接口定义。

 

 字节缓冲区通过ioBuffer分配堆外内存:

 NIO框架详细说明:内蒂的高性能道路_7.png

 

 读写Socket IO时,为了避免将副本从堆内存复制到直接内存,Netty的ByteBuf分配器直接创建非堆内存避免缓冲区的二级副本,并通过“零副本”提高读写性能。

 

 让我们继续看第二个“零拷贝”实现,CompositeByteBuf,它从外部将多个字节组封装成一个字节组,并提供一个统一封装的字节组接口。其类定义如下(CompositeByteBuf类继承关系):

 NIO框架详细说明:内蒂的高性能道路_8.png

 

 从继承关系中,我们可以看到CompositeByteBuff实际上是ByteBuff的一个包装器,它将多个ByteBuff组合成一个集合,然后向外部提供一个统一的ByteBuff接口,相关的定义如下(CompositeByteBuff类定义):

 NIO框架详细说明:内蒂的高性能道路_9.png

 

 添加字节组,不需要内存拷贝,相关代码如下(添加字节组的“零拷贝”):

 NIO框架详细说明:内蒂的高性能道路_10.png

 

 最后,让我们看看文件传输的“零拷贝”:

 NIO框架详细说明:内蒂的高性能道路_11.png

 

 网络文件传输默认文件区域通过传输到方法将文件发送到目标通道。下面重点介绍文件通道的转储方法,其应用编程接口文档描述如下:

 NIO框架详细说明:内蒂的高性能道路_12.png

 

 对于许多操作系统来说,它直接将文件缓冲区的内容发送到目标通道,不需要拷贝,这是一种更有效的传输方式,实现了文件传输的“零拷贝”。

 3.3内存池

 

 随着JVM虚拟机和JIT实时编译技术的发展,对象分配和回收是一项非常轻量级的任务。但是对于缓冲区缓冲区,情况略有不同,特别是在堆外分配和恢复直接内存,这是一个耗时的操作。为了尽可能重用缓冲区,Netty提供了一种基于内存池的缓冲区重用机制。让我们来看看Netty ByteBuf的实现:

 NIO框架详细说明:内蒂的高性能道路_1.png

 

 Netty提供了多种内存管理策略。通过在启动辅助类中配置相关参数,可以实现差异化定制。让我们通过性能测试来看看基于内存池回收的字节流和普通字节流之间的性能差异。

 

 用例一,使用内存池分配器创建直接内存缓冲区:

 NIO框架详细说明:内蒂的高性能道路_2.png

 

 用例2,非堆内存分配器创建的直接内存缓冲区:

 NIO框架详细说明:内蒂的高性能道路_3.png

 

 每次执行300万次,性能比较结果如下:

 NIO框架详细说明:内蒂的高性能道路_4.png

 

 性能测试表明,带内存池的ByteBuff的性能比ByteBuff高约23倍(性能数据与使用场景密切相关)。

 

 让我们简要分析一下网络内存池的内存分配:

 NIO框架详细说明:内蒂的高性能道路_5.png

 

 继续看新的DirectBuffer方法,我们发现它是一个抽象方法,由AbstractByteBufAllocator的子类实现,代码如下:

 NIO框架详细说明:内蒂的高性能道路_6.png

 

 代码跳转到PooledByteBufAllocator的newDirectBuffer方法,从缓存中获取内存区域PoolArena,并调用其分配方法来分配内存:

 NIO框架详细说明:内蒂的高性能道路_7.png

 

 PoolArena的分配方法如下:

 NIO框架详细说明:内蒂的高性能道路_8.png

 

 我们重点分析了newByteBuf的实现,这也是一个抽象的方法。不同类型的缓冲区分配由DirectArena和HeapArena子类实现,因为测试用例使用堆外内存:

 NIO框架详细说明:内蒂的高性能道路_9.png

 

 因此,它关注于DirectArena的实现:如果sun的不安全没有启用,那么:

 NIO框架详细说明:内蒂的高性能道路_10.png

 

 用下面的代码执行PooledDirectByteBuf的新实例方法:

 NIO框架详细说明:内蒂的高性能道路_11.png

 

 使用RECYCLER的get方法来回收ByteBuf对象,如果是一个非内存池实现,直接创建一个新的ByteBuf对象。从缓冲池中获取ByteBuf后,调用AbstractReferenceCountedbytebuf的setRefCnt方法来设置引用计数器,该计数器用于对象引用计数和内存回收(类似于JVM垃圾收集机制)。

 3.4高效反应器螺纹模型

 

 有三种常用的反应器螺纹模型,如下所示:

 反应器单线程模型;

 反应器多线程模型;

 主从反应堆多线程模型。

 

 反应器单线程模型意味着所有的输入输出操作都在同一个NIO线程上完成,NIO线程的职责如下:

 作为NIO服务器,从客户端接收TCP连接;

 作为NIO客户端,启动到服务器的传输控制协议连接;

 读取通信对方的请求或响应消息;

 向通信对方发送消息请求或响应消息。

 

 反应器单线模型示意图如下:

 NIO框架详细说明:内蒂的高性能道路_1.png

 

 因为反应堆模式使用异步非阻塞输入输出,所以所有输入输出操作都不会导致阻塞。理论上,一个线程可以独立处理所有与IO相关的操作。从架构的角度来看,NIO线程可以真正履行它的职责。例如,接受者从客户端接收到TCP连接请求消息,在链接成功建立后,相应的字节缓冲区通过调度被调度到指定的处理程序以解码该消息。用户处理器可以通过NIO线程向客户端发送消息。

 

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

 一个NIO线程可以同时处理数百个链接,并且它的性能不被支持。即使NIO线程的CPU负载达到100%,也不能满足海量消息的编码、解码、读取和发送;

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

 可靠性问题:一旦NIO线程意外运行或进入无限循环,整个系统的通信模块将不可用,无法接收和处理外部消息,导致节点故障。

 

 为了解决这些问题,反应器多线程模型得到了发展。让我们一起学习反应器多线程模型。

 

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

 NIO框架详细说明:内蒂的高性能道路_3.png

 

 反应器多线程模型的特点;

 有一个特殊的NIO线程-接受者线程来监控服务器并接收来自客户端的TCP连接请求;

 网络输入输出操作——读写等。由一个NIO线程池负责,该线程池可以由一个标准的JDK线程池实现,该线程池包含一个任务队列和N个可用线程,这些NIO线程负责读取、解码、编码和发送消息;

 一个NIO线程可以同时处理N个链接,但是一个链接只对应一个NIO线程,这就阻止了并发操作。

 

 在大多数情况下,反应器多线程模型能够满足性能要求;然而,在非常特殊的应用场景中,NIO线程负责监听和处理所有客户端连接,这可能会导致性能问题。例如,数百万个客户端同时连接,或者服务器需要对客户端的握手消息执行安全身份验证,而身份验证本身就失去了性能。在这种情况下,单个接受者线程可能没有足够的性能。为了解决性能问题,产生了第三反应器线程模型——主从反应器多线程模型。

 

 主从反应堆线程模型的特点是服务器不是一个单一的NIO线程,而是一个独立的NIO线程池,用于接收客户端连接。在接收到客户端TCP连接请求(可能包括访问认证等)的处理完成之后,接受者将新创建的套接字通道注册到IO线程池(子反应器线程池)中的IO线程。),它负责读取、写入、编码和解码套接字通道。接受者线程池仅用于客户端登录、握手和安全身份验证。一旦链接成功建立,该链接将注册到后端子反应器线程池中的IO线程,IO线程负责后续的IO操作。

 

 其线程模型如下图所示:

 NIO框架详细说明:内蒂的高性能道路_4.png

 

 使用主从NIO线程模型,我们可以解决一个服务器监听线程不能有效处理所有客户端连接的问题。因此,在Netty的官方演示中,建议使用这个线程模型。

 

 事实上,内蒂的线程模型是不固定的。通过在启动辅助类中创建不同的EventLoopGroup实例并配置适当的参数,可以支持上述三种反应器线程模型。正是因为Netty对Reactor线程模型的支持提供了灵活的定制能力,所以它可以满足不同业务场景的性能需求。

 3.5无锁定的串行设计概念

 

 在大多数情况下,并行多线程可以提高系统的并发性能。但是,如果对共享资源的并发访问处理不当,就会带来严重的锁竞争,最终导致性能下降。为了尽可能避免锁竞争造成的性能损失,可以采用序列化设计,即消息处理尽可能在同一个线程内完成,不需要进行线程切换,从而避免多线程竞争和同步锁。

 

 为了尽可能提高性能,Netty采用了串行无锁设计,并在IO线程内执行串行操作,以避免多线程竞争导致的性能下降。从表面上看,序列化设计似乎具有较低的CPU利用率和不足的并发性。但是,通过调整NIO线程池的线程参数,可以启动多个序列化线程同时并行运行。与一个队列-多个工作线程模型相比,这种局部解锁的串行线程设计具有更好的性能。

 

 Netty系列化设计的工作原理图如下:

 NIO框架详细说明:内蒂的高性能道路_5.png

 

 在读取消息后,Netty的NioEventLoop直接调用通道管道的fireChannelRead(对象消息)。只要用户不主动切换线程,NioEventLoop将总是调用用户的处理程序而不切换线程。这种序列化处理方法避免了多线程操作导致的锁竞争,从性能角度来看是最佳的。

 3.6高效并发编程

 

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

 大量正确使用易挥发物质;

 化学文摘社和原子类的广泛使用;

 使用线程安全容器;

 通过读写锁提高并发性。

 

 如果你想知道Netty高效并发编程的细节,你可以阅读我之前在微博上分享的Netty多线程并发编程应用分析。本文详细介绍和分析了Netty的多线程技术及其应用。

 3.7高性能序列化框架

 

 影响序列化性能的关键因素总结如下:

 序列化码流大小(占用网络带宽);

 序列化和反序列化性能(CPU资源占用);

 是否支持跨语言(异构系统的对接和开发语言的切换)。

 

 默认情况下,Netty支持谷歌原型。通过扩展Netty的编解码器接口,用户可以实现其他高性能的序列化框架,如节俭的压缩二进制编解码器框架。

 

 让我们来比较一下由不同的序列化和反序列化框架序列化的字节数组:

 NIO框架详细说明:内蒂的高性能道路_6.png

 

 从上图可以看出,由Protobuf序列化的流只有由Java序列化的流的1/4。正是Java本机序列化的低性能导致了各种高性能开源序列化技术和框架(低性能只是原因之一,还有其他因素,如跨语言和IDL定义)。

 3.8灵活的传输控制协议参数配置能力

 

 在某些情况下,合理地设置传输控制协议参数可以提高性能。如果设置不当,将对性能产生很大影响。

 

 下面,我们总结了几个对性能有很大影响的配置项目:

 SO _ RCVBUF和SO _ SNDBUF:一般推荐值为128K或256K;

 算法通过自动连接缓冲区中的小数据包形成大数据包,防止大量小数据包阻塞网络,提高了网络应用的效率。然而,对于时延敏感的应用场景,需要关闭优化算法。

 软中断:如果Linux内核版本支持RPS(高于RPS(2.6.35),在RPS开启后可以实现软中断,提高网络吞吐量。RPS根据数据包的源地址、目的地址、目的和源端口计算哈希值,然后根据该哈希值选择运行软中断的cpu。从上层来看,这意味着每个连接都与cpu绑定,哈希值用于平衡多个CPU上的软中断,从而提高网络的并行处理性能。

 

 Netty可以在启动辅助类中灵活配置TCP参数,以满足不同的用户场景。相关的配置接口定义如下:

 NIO框架详细说明:内蒂的高性能道路_7.png

 四.本文摘要

 

 通过分析Netty的体系结构和性能模型,我们发现Netty体系结构的高性能是经过精心设计和实现的。由于高质量的体系结构和代码,Netty支持10W TPS的跨节点服务调用并不是很困难。

 V.作者简介

 

 NIO框架详细说明:内蒂的高性能之路_阿杰普李林峰

 

 他于2007年毕业于东北大学,2008年进入华为建立高性能通信软件

 我在NIO设计和开发方面有6年的经验,我精通NIO框架,如Netty和Mina。

 网易中国社区创始人,《网易权威指南》作者。

 联系方式:新浪微博网购微信:网购。

哇谷im_im即时通讯_私有云_公有云-哇谷云科技官网-JM沟通

IM下载体验 - 哇谷IM-企业云办公IM即时聊天社交系统-JM 沟通下载

IM功能与价格 - 哇谷IM-提供即时通讯IM开发-APP搭建私有化-公有云-私有化云-海外云搭建

新闻动态 - 哇谷IM-即时通讯热门动态博客聊天JM沟通APP

哇谷IM-JM沟通热门动态博客短视频娱乐生活

关于哇谷-哇谷IM-提供企业即时通讯IM开发-语音通话-APP搭建私有化-公有云-私有化云-海外云搭建

联系我们 - 哇谷IM-即时通讯IM私有化搭建提供接口与SDK及哇谷云服务

即时通讯IM融云世界

IM即时通讯钉钉技术:企业IM钉钉在后端架构上的优越之处

新的市场叫板环信、融云、腾讯云!开源版IM即使聊天工具

企业IM即时通讯聊天办公APP钉钉技术分析交流

哇谷云-怎么样正确认识海外云服务器

公有云和私有云之间有什么区别?类似融云、环信云、网易云、哇谷云?

im即时通讯社交软件APP红包技术分析(五):微信红包、聊呗红包、诚信红包、高并发技术