最后更新:2022-01-26 09:55
网络通讯的高性能源自以下方面的设计细节
用什么样的通道将数据发送给对方,是BIO、NIO还是AIO,I/O传输模型在很大程度上决定了框架的性能。
用什么样的通讯协议,是HTTP,还是内部私有协议。协议的选择不同,性能也就不同,一般来说内部私有协议比公有协议的性能更高。
线程模型涉及如何读取数据包,读取之后的编解码在哪个线程中进行,编解码后的消息如何派发等方面。线程模型设计不同,对性能也会产生非常大的影响。
非阻塞:调用I/O的线程不会被挂起,可以在无I/O事件到来时干其他工作,一个线程可以同时处理多个客户端I/O事件
异步:这里说netty是异步的,其实不完全正确,异步还是同步在我的另一篇文章有介绍,主要是看【请求的数据是需要用户线程主动去系统内核获取的还是由操作系统直接从内核空间内存拷贝到用户空间内存的】
netty的零拷贝主要体现在如下三个地方:

其中if(PlatformDependent.hasUnsafe)会根据配置文件、操作系统是否支持等条件综合返回true/false
对于对外直接内存,创建和回收是一种耗时工作,netty设计了一套基于内存池的【缓冲区重用机制】,PooledByteBuf(子类有池化直接的ByteBuf与池化间接的ByteBuf)
reactor线程模型有三种:
a)图

b)特点
c) 使用场景:
并发量较小的业务场景,不适合高负载、高并发的场景
d) 缺点
Reactor多线程模型与单线程模型最大的区别就是设计了一个NIO线程池处理I/O操作。
a)图

b)特点
c) 使用场景:
绝大多数场景下,Reactor多线程模型都可以满足性能需求。
d) 缺点
在及特殊的应用场景,单个acceptor线程负责监听和分发所有客户端连接,会存在性能问题:
【主从Reactor多线程模型】与【Reactor多线程模型】的区别是:服务端用于接收客户端连接的不再是单个NIO线程,而是分配了一个独立NIO线程池。
a)图

b)特点
c) 使用场景:
可以解决一个服务端的监听线程无法有效处理所有客户端连接的性能不足问题。(Netty官方Demo推荐使用这种线程模型)
通过启动辅助类(BootStrap)中创建不同的EventLoopGroup+配置不同参数,就可以自由选择上述三种Reactor线程模型,以满足不同业务场景【性能】需求。不同线程模型,netty提供了统一的编程模型,这一点体现了其设计的精妙、强大之处。
说明:多线程可以提升性能,但是对于共享资源的访问需要加锁以保证对共享资源并发访问的正确性,使用锁有可能造成严重的【锁竞争】,导致性能下降。
netty在设计上采用无锁化串行设计,在某一个I/O线程内部进行串行处理,避免多线程竞争共享资源导致性能下降(局部无锁化的串行线程设计性能更优——通过业务边界进行划分:将读取一次数据---Inboundhandler1---Inboundhandler2 或者 outboundhandler1---outboundhandler2---发送数据放在一个线程中进行,避免将这个过程放入多个线程导致对共享资源的访问造成锁的竞争)
表面上看,似乎串行的设计对CPU利用率不高,并发程度不够,但是通过调整NIO线程池的线程数量,可以同时启动【多个串行的线程】【并行运行】,这种局部无锁化的串行线程设计,相比【一个队列--多个工作线程】的模型性能更优。

上图中用户不主动切换线程是指:使用netty的用户可以在handler中将具体处理业务交给自己定义的其他线程处理。
CAS的思想很简单:三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。
JVM中的CAS操作正是利用了提到的处理器提供的CMPXCHG指令实现的;循环CAS实现的基本思路就是循环进行CAS操作直到成功为止;
影响序列化性能的关键因素:
还有一项因素也比较关键:是否支持跨语言
1、netty默认支持:protobuf
2、通过扩展netty的编解码接口可以接入其他高性能序列化框架进行编解码
市面上流行的序列化框架序列化后码流大小:

可见,java序列化是protobuf的4倍
通过netty启动辅助类可以灵活地配置TCP参数,满足不同的用户场景。
正当设置TCP参数在某些场景下对于性能的提升可以起到明显的效果,例如SO_RCVBUF和SO_SNDBUF。假如设置不当,对性能的影响是非常大的。下面我们总结下对性能影响比较大的几个配置项:
netty常用的配置见:P121
本文由 在线网速测试 整理编辑,转载请注明出处。