Netty高性能之道

时间:2022-07-24
本文章向大家介绍Netty高性能之道,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

【导读】传统RPC性能差的原因有三个,一是网络传输方式是同步阻塞的,二是Java原生序列化性能差,无法跨语言使用,序列化之后体积大等,三是线程模型会占用大量系统资源。所以今天来看以下Netty的高性能是如何建立的?

IO通信的三原则:

1、传输:用什么样的通道发送数据,I/O模型在很大程度上决定了通信的性能。

2、协议:协议的选择不同,性能也不同。相比于公有协议,内部私有协议的性能通常往往更佳。

3、线程:数据报如何读取,编解码在哪个线程执行,Reactor线程模型的不同,性能也不同。

Netty高性能之道:

一、异步非阻塞通信

I/O多路复用技术通过把多个I/O的阻塞复用到同一个select的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求,与传统的BIO相比,多路复用的最大优势就是系统开销小,无需创建额外的线程。

NIO有阻塞和非阻塞模式,一般来说,低负载,低并发可以选择阻塞模式降低复杂度,高负载、高并发需选择非阻塞模式来撑起系统的性能。

二、高效的Reactor线程模型

常用的Reactor线程模式有三种,分别是:

1、Reactor单线程模型

2、Reactor多线程模型

3、主从Reactor多线程模型

(1)Reactor单线程模型

NioEventLoopGroup group = new NioEventLoopGroup(1);ServerBootstrap server = new ServerBootstrap();server.group(group);

所有的IO操作都在同一个NIO线程上完成,NIO线程职责是接收或发起TCP连接,读取或发送消息。

由于Reactor模式采用的是异步非阻塞IO,所有的IO操作都不会导致阻塞,理论上一个线程就可以独立处理所有IO相关的操作。

此模式不适用于高并发、高负载的场景,原因如下:

1、一个NIO线程同时处理成百上千的链路,性能上无法支撑

2、当负载过重时,处理速度将会变慢,会导致大量客户端连接超时,超时之后往往会进行重发,最终导致大量消息积压和处理超时。

3、可靠性问题,如果NIO线程进入了死循环,会导致不可用。

为了解决上述问题,就有了Reactor多线程模型

(2)Reactor多线程模型

NioEventLoopGroup acceptor = new NioEventLoopGroup(1);NioEventLoopGroup worker = new NioEventLoopGroup();ServerBootstrap server = new ServerBootstrap();server.group(acceptor,worker);

其于单线程模型最大的区别就是有一个Acceptor线程单独处理连接请求,有一组NIO线程负责I/O读写。

在绝大多数场景下,多线程模型可以满足性能需求,但是在高并发的情况下一个NIO线程处理连接请求可能会导致性能问题,为了解决此问题,产生了主从Reactor线程模型。

(3)主从Reactor线程模型

NioEventLoopGroup boss = new NioEventLoopGroup();NioEventLoopGroup worker = new NioEventLoopGroup();ServerBootstrap server = new ServerBootstrap();server.group(boss,worker);

服务端用于接收连接请求的不再是一个单独的NIO线程,而是一个独立的线程池。可解决一个服务端无法有效处理所有连接请求的问题。推荐使用该模型。

三、无锁的串行化设计

并行多线程处理可以提升系统的并发能力,但是,如果处理不当,会有锁竞争的问题。为了解决此问题,Netty通过串行化设计,也就是消息的处理在同一个线程内完成,不进行上下文切换,避免了多线程竞争和同步锁。也就是ChannelPipeline的设计。

Netty的NioEventLoop在读取到消息之后,直接调用ChannelPipeline的fireChannelRead方法,就会一直按照责任链模式执行下去,期间不进行线程切换。

四、高效的并发编程

主要体现在如下几点:

1、Volatile的大量、正确使用

2、CAS和原子类的广泛使用

3、线程安全容器的使用

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

五、高性能的序列化框架

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

1、序列化之后码流的大小(网络带宽的占用)

2、序列化与反序列化的性能(CPU资源的占用)

3、是否支持跨语言

Netty提供了对Google ProtoBuf、Thrift等优秀序列化框架的支持,之后篇章讲解用法。

六、零拷贝

何为零拷贝?数据都存储在JVM内存里面,如果需要进行数据传输,需要将JVM里面的数据拷贝一份到内核空间中,而NIO提供了在堆外内存存放数据的功能,可以直接进行传输,不需要进行拷贝。

Netty的零拷贝主要体现在三个方面:

(1)Netty的接受和发送都使用DirectBuffers,使用堆内直接内存进行socket读写,不需要进行字节缓冲区的二次拷贝。

(2)CompositeByteBuf,对外将多个ByteBuf封装成一个ByteBuf,对外提供统一的ByteBuf接口。是一个组合Buffer对象,避免了通过内存拷贝的方式将几个小Buffer合并成一个大Buffer。

(3)文件传输,Netty的文件传输类DefaultFileRegion通过transferTo方法将文件发送到目标Channel中。避免了传统通过循环write方式导致的内存拷贝问题。

七、内存池

JVM提供了对内存的分配和回收,但是堆内内存的分配和回收都是比较耗时的操作,为了重用缓冲区,Netty提供了基于内存池的缓冲区重用机制。

通过内存池分配器创建直接内存缓冲区:

PooledByteBufAllocator.DEFAULT.directBuffer(1024);

八、灵活的TCP参数配置

合理的TCP参数在特定场景对于性能的提升可以有显著的效果,例如:

(1)SO_RCVBUF和SO_SNDBUF:通常建议设置为128kb或者256kb

(2)SO_TCPNODELAY:NAGLE算法通过将缓冲区内的小封包自动相连,阻止大量小封包的发送阻塞网络,从而提升网络应用效率,但对于时效强的场景应关闭此算法。

上述就是Netty高性能的基础,来自《Netty权威指南 第2版》一书。