netty 之 高性能原因(理论)

一、说明

网络通讯的高性能源自以下方面的设计细节

1、I/O传输模型

用什么样的通道将数据发送给对方,是BIO、NIO还是AIO,I/O传输模型在很大程度上决定了框架的性能。

2、数据协议

用什么样的通讯协议,是HTTP,还是内部私有协议。协议的选择不同,性能也就不同,一般来说内部私有协议比公有协议的性能更高。

3、线程模型

线程模型涉及如何读取数据包,读取之后的编解码在哪个线程中进行,编解码后的消息如何派发等方面。线程模型设计不同,对性能也会产生非常大的影响。

二、Netty高性能原因

1、异步非阻塞通讯

非阻塞:调用I/O的线程不会被挂起,可以在无I/O事件到来时干其他工作,一个线程可以同时处理多个客户端I/O事件

异步:这里说netty是异步的,其实不完全正确,异步还是同步在我的另一篇文章有介绍,主要是看【请求的数据是需要用户线程主动去系统内核获取的还是由操作系统直接从内核空间内存拷贝到用户空间内存的】

2、零拷贝

netty的零拷贝主要体现在如下三个地方:

  • netty接收与发送的ByteBuffer采用的是DirectBuffer(直接缓冲区、属于JVM堆外内存),不需要将数据从操作系统内核缓冲区拷贝到用户缓冲区这个过程。
    • 源码见:AbstractNioByteChannel的NioByteUnsafe内部类的read()方法中
      byteBuf = allocHandle.allocate(allocator),然后调用的是io.netty.channel.DefaultMaxMessagesRecvByteBufAllocator中内部类MaxMessageHandle的allocate方法,然后调用的是AbstractByteBufAllocator的ioBuffer方法,方法中如下代码逻辑:

其中if(PlatformDependent.hasUnsafe)会根据配置文件、操作系统是否支持等条件综合返回true/false

  • netty提供了组合Buffer机制,可以聚合多个ByteBuffer,操作组合后对象就可以,不需要将数据从小的ByteBuffer拷贝至另一个大大ByteBuffer
    • 源码见:CompositeByteBuf间接集成ByteBuf
  • netty中文件传输采用了transferTo方法,可以直接将【文件缓冲区】的数据发送到目标Channel,避免传统的while循环方式导致的数据拷贝
    • 源码见:FileChannel的transferTo方法

3、内存池

对于对外直接内存,创建和回收是一种耗时工作,netty设计了一套基于内存池的【缓冲区重用机制】,PooledByteBuf(子类有池化直接的ByteBuf与池化间接的ByteBuf)

4、高效的Reactor线程模型

reactor线程模型有三种:

  • Reactor单线程模型
  • Reactor多线程模型
  • 主从Reactor多线程模型

1)Reactor单线程模型

a)图

b)特点

  • 线程模型中只有【一个线程】处理所有的【接收连接请求】、【接收消息】、【编码】、【解码】、【读取】、【发送消息】,(处理业务逻辑一般也在这个线程里完成)这一个NIO线程的职责:
    • 作为NIO服务端,接收客户端TCP连接(服务端职责)
    • 作为NIO客户端,向服务端发起TCP连接(客户端职责)
    • 通过Dispatcher派发到指定的handler
    • 通过Handler解码、处理业务逻辑、进行发送前编码
    • 接收消息:读取通信对端的请求或者应答消息
    • 发送消息:向通信对端发送消息请求或者应答消息
  • 由于Reactor模式使用的是NIO,所有的I/O操作都不会阻塞,理论上一个线程可以独立处理所有I/O相关操作。从架构层面上看,一个NIO线程确实可以完成上面的职责

c) 使用场景:

并发量较小的业务场景,不适合高负载、高并发的场景

d) 缺点

  • 一个线程无法处理很多的编码、解码、读取、发送:一个NIO线程如果同时处理成百上千链路,机器性能无法满足,即使NIO线程的CPU负载达到100%,也无法满足消息的编码、解码、读取、发送。
  • 恶性循环:如果NIO线程的CPU负载过重,那么处理速度将变慢,从而导致大量客户端连接超时,超时导致重发,更加重NIO线程的负载,最终会导致消息积压与处理超时,这样NIO线程就会成为系统的性能瓶颈。
  • 单点故障:一旦NIO线程发生意外或者进入死循环,会导致整个系统通信模块不可用

2) Reactor多线程模型

Reactor多线程模型与单线程模型最大的区别就是设计了一个NIO线程池处理I/O操作。

a)图

b)特点

  • 服务端有一个专门的NIO线程accept客户端的TCP连接请求
  • 所有客户端的网络I/O的读/写等操作,由一个NIO线程池负责,由这些NIO线程负责消息的【读取】、【解码】、【编码】、【发送】;可以采用标准的JDK线程池来实现(它包含了一个任务队列和多个可用的线程)
  • 线程池中的一个NIO线程可以同时处理多条请求链路,但是一条链路只对应一个NIO线程,防止发生并发问题

c) 使用场景:

绝大多数场景下,Reactor多线程模型都可以满足性能需求。

d) 缺点

在及特殊的应用场景,单个acceptor线程负责监听和分发所有客户端连接,会存在性能问题:

  • 百万客户端并发连接
  • 服务端需要对客户端的握手消息进行安全认证,认证本身消耗性能很大

3) 主从Reactor多线程模型

【主从Reactor多线程模型】与【Reactor多线程模型】的区别是:服务端用于接收客户端连接的不再是单个NIO线程,而是分配了一个独立NIO线程池。

a)图

 

b)特点

  • 服务端accept不在是一个NIO线程,而是一个NIO线程池(图中Accept Pool)
    • accept线程池中线程负责:①接收客户端TCP连接请求②握手、接入、tcp连接安全认证等
  • 完成连接后,将客户端SocketChannel注册到I/O线程池(图中I/O pool)的某个I/O线程上
    • I/O线程池中线程负责:【读取】、【解码】、【编码】、【发送】

c) 使用场景:

可以解决一个服务端的监听线程无法有效处理所有客户端连接的性能不足问题。(Netty官方Demo推荐使用这种线程模型)

4) netty支持情况

通过启动辅助类(BootStrap)中创建不同的EventLoopGroup+配置不同参数,就可以自由选择上述三种Reactor线程模型,以满足不同业务场景【性能】需求。不同线程模型,netty提供了统一的编程模型,这一点体现了其设计的精妙、强大之处。

5、无锁化的串行设计理念

说明:多线程可以提升性能,但是对于共享资源的访问需要加锁以保证对共享资源并发访问的正确性,使用锁有可能造成严重的【锁竞争】,导致性能下降。

netty在设计上采用无锁化串行设计,在某一个I/O线程内部进行串行处理,避免多线程竞争共享资源导致性能下降(局部无锁化的串行线程设计性能更优——通过业务边界进行划分:将读取一次数据---Inboundhandler1---Inboundhandler2 或者 outboundhandler1---outboundhandler2---发送数据放在一个线程中进行,避免将这个过程放入多个线程导致对共享资源的访问造成锁的竞争)

表面上看,似乎串行的设计对CPU利用率不高,并发程度不够,但是通过调整NIO线程池的线程数量,可以同时启动【多个串行的线程】【并行运行】,这种局部无锁化的串行线程设计,相比【一个队列--多个工作线程】的模型性能更优。

上图中用户不主动切换线程是指:使用netty的用户可以在handler中将具体处理业务交给自己定义的其他线程处理。

6、高效的并发编程技巧的使用

  • volatile关键字的大量且正确使用
  • CAS和原子类的广泛使用:(自旋锁)

    CAS的思想很简单:三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。
    JVM中的CAS操作正是利用了提到的处理器提供的CMPXCHG指令实现的;循环CAS实现的基本思路就是循环进行CAS操作直到成功为止;

  • 线程安全容器的使用
  • 通过读写锁提升并发性能

7、对高性能序列化框架的支持

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

  • 序列化后的【码流大小】:影响传输时带宽的占用
  • encode/decode的性能:CPU资源占用

还有一项因素也比较关键:是否支持跨语言

netty对序列化框架的支持情况

1、netty默认支持:protobuf

2、通过扩展netty的编解码接口可以接入其他高性能序列化框架进行编解码

市面上流行的序列化框架序列化后码流大小:

可见,java序列化是protobuf的4倍

8、灵活的TCP参数配置能力

通过netty启动辅助类可以灵活地配置TCP参数,满足不同的用户场景。

正当设置TCP参数在某些场景下对于性能的提升可以起到明显的效果,例如SO_RCVBUF和SO_SNDBUF。假如设置不当,对性能的影响是非常大的。下面我们总结下对性能影响比较大的几个配置项:

  • SO_RCVBUF和SO_SNDBUF:接收/发送缓冲区大小,通常建议值为128K或者者256K;
  • SO_TCPNODELAY:NAGLE算法通过将缓冲区内的小封包自动相连,组成较大的封包,阻止大量小封包的发送阻塞网络,从而提高网络应用效率(可以理解为将小的多次数据包合并成1个大的数据包一次传输)。但是对于时延敏感的应用场景需要关闭该优化算法;netty默认禁用该算法,从而使报文传输延时最小化
  • 软中断:假如Linux内核版本支持RPS(2.6.35以上版本),开启RPS后可以实现软中断,提升网络吞吐量。RPS根据数据包的源地址,目的地址以及目的和源端口,计算出一个hash值,而后根据这个hash值来选择软中断运行的cpu,从上层来看,也就是说将每个连接和cpu绑定,并通过这个hash值,来均衡软中断在多个cpu上,提升网络并行解决性能(可以理解为客户端连接负载均衡到CPU)

netty常用的配置见:P121

 

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值