引入
问题:网络通信在RPC调用中起到什么作用呢?
回答:
- RPC是解决进程间通信的一种方式。一次RPC调用,本质就是服务器消费者和服务提供者间的一次信息交换的过程。
- 服务调用者通过网络IO发送一条请求消息,服务提供者接收并解析,处理完相关的业务逻辑之后,再发送一条响应消息给服务调用者,服务调用者接收并解析响应消息,处理完相关的响应逻辑,一次RPC调用就结束了
- 可以说,网络通信是整个RPC调用流程的基础
总结:RPC是用来解决两个应用之间的通信的,而网络是两台机器之间的“桥梁”,只有架好了桥梁,我们才能把请求数据从一端传输到另一端。关于网络通信,只要记住一个关键词就行了——可靠的传输
RPC调用在大多数情况下,是一个高并发调用的场景,因此倾向于使用IO多路复用模型。开发语言的网络通信框架的选项上,我们最优的选择是基于reactor模型实现的框架,比如java选择netty
零拷贝
- 零拷贝是系统层面上的零拷贝,主要目标是避免用户空间和内核空间之间的数据拷贝操作,可以提升CPU的利用率
- 而netty的零拷贝则不太一样,它完全站在了用户空间上,其拷贝技术主要是偏向于数据操作的优化上
那么 Netty 这么做的意义是什么呢?
- 在网络传输过程中,RPC并不会把请求参数的所有二进制数据整体一下子发送到对端机器上,中间可能会拆分成好几个数据包,也可能会合并其他请求的数据包,所以消息都需要有边界。一端的机器收到消息之后,就需要对数据包进行处理,根据边界对数据包进行分隔和合并,最终获得一条完整的消息
- 那收到消息后,对数据包的分隔和合并,是在用户空间完成的,还是在内核空间完成的呢?
- 当然是在用户空间,因为对数据包的处理工作都是由应用程序来处理的,那么这里有没有可能存在数据的拷贝操作?可能会存在,当然不是在用户空间和内核空间之间的拷贝,是用户空间内部内存中的拷贝处理操作。Netty 的零拷贝就是为了解决这个问题,在用户空间对数据操作进行优化。
那么 Netty 是怎么对数据操作进行优化的呢?
- netty提供了CompositeByteBuf类,它可以将多个ByteBuf合并为一个逻辑上的ByteBuf,避免了各个ByteBuf之间的拷贝
- ByteBuf支持slice操作,因此可以将ByteBuf分解为多个共享同一个存储区域的ByteBuf,避免了内存的拷贝
- 通过wrap操作,我们可以将byte[]数组,ByteBuf、ByteBuffer 等包装成一个 NettyByteBuf 对象, 进而避免拷贝操作。
Netty 框架中很多内部的 ChannelHandler 实现类,都是通过CompositeByteBuf、 slice、wrap 操作来处理 TCP 传输中的拆包与粘包问题的。
那么 Netty 有没有解决用户空间与内核空间之间的数据拷贝问题的方法呢?
- Netty 的 ByteBuffer 可以采用 Direct Buffers,使用堆外直接内存进行 Socketd 的读写操作,最终的效果虚拟内存所实现的效果是一样的。
- Netty 还提供 FileRegion 中包装 NIO 的 FileChannel.transferTo() 方法实现了零拷贝,这与 Linux 中的 sendfile 方式在原理上也是一样的。
小结
零拷贝的好处是避免了没必要的CPU拷贝,让CPU解脱出来去做其他的事,同时也减少了CPU在内核空间和用户空间之间的上下文切换,从而提升了网络通信效率和应用程序整体的性能
而 Netty 的零拷贝与操作系统的零拷贝是有些区别的,Netty的零拷贝倾向于用户空间中对数据操作的优化,这对处理TCP传输中的拆包粘包问题有着重要的意义,对应用程序处理请求数据和返回数据也有重要意义