Geek漫游指南

RPC框架

date
slug
introduction-to-rpc-framework
author
status
Public
tags
技术分享
summary
type
Post
thumbnail
category
💻 Backend
updatedAt
Oct 29, 2023 10:06 AM
为什么RPC框架不使用HTTP框架?
  1. HTTP请求除了请求数据还包含了数据包,以及换行、回车符等
  1. HTTP属于无状态协议,客户端无法对请求和响应进行关联
  1. 每次请求都需要重新建立连接,响应完成后再关闭连接。对于高性能的RPC来说,性能很慢满足
 
RPC框架
  1. TCP连接
  1. 通过序列化的二进制数据进行传输
  1. 需要设计一个可扩展且向后兼容的传输协议
 
序列化问题
Java类什么情况下需要实现序列化?
当需要转换成二进制字节流时,而像Feign这种调用入参出参是不需要序列化的。
JSON 进行序列化有这样两个问题,你需要格外注意:
  1. JSON 进行序列化的额外空间开销比较大,对于大数据量服务这意味着需要巨大的内存和磁盘开销;
  1. JSON 没有类型,但像 Java 这种强类型语言,需要通过反射统一解决,所以性能不会太好。
如果 RPC 框架选用 JSON 序列化,服务提供者与服务调用者之间传输的数据量要相对较小,否则将严重影响性能
 
HessianHessian 是动态类型、二进制、紧凑的,并且可跨语言移植的一种序列化框架。
Hessian 协议要比 JDK、JSON 更加紧凑,性能上要比 JDK、JSON 序列化高效很多,而且生成的字节数也更小。
使用代码示例如下:
Student student = new Student(); student.setNo(101); student.setName("HESSIAN"); //把student对象转化为byte数组 ByteArrayOutputStream bos = new ByteArrayOutputStream(); Hessian2Output output = new Hessian2Output(bos); output.writeObject(student); output.flushBuffer(); byte[] data = bos.toByteArray(); bos.close(); //把刚才序列化出来的byte数组转化为student对象 ByteArrayInputStream bis = new ByteArrayInputStream(data); Hessian2Input input = new Hessian2Input(bis); Student deStudent = (Student) input.readObject(); input.close(); System.out.println(deStudent);
相对于 JDK、JSON,由于 Hessian 更加高效,生成的字节数更小,有非常好的兼容性和稳定性,所以 Hessian 更加适合作为 RPC 框架远程通信的序列化协议。
但 Hessian 本身也有问题,官方版本对 Java 里面一些常见对象的类型不支持,
比如:
Linked 系列,LinkedHashMap、LinkedHashSet 等,但是可以通过扩展 CollectionDeserializer 类修复;
Locale 类,可以通过扩展 ContextSerializerFactory 类修复;
Byte/Short 反序列化的时候变成 Integer。
以上这些情况,你在实践时需要格外注意。
 
Protobuf
Protobuf 是 Google 公司内部的混合语言数据标准,是一种轻便、高效的结构化数据存储格式,可以用于结构化数据序列化,支持 Java、Python、C++、Go 等语言。Protobuf 使用的时候需要定义 IDL(Interface description language),然后使用不同语言的 IDL 编译器,生成序列化工具类,它的优点是:
序列化后体积相比 JSON、Hessian 小很多;
IDL 能清晰地描述语义,所以足以帮助并保证应用程序之间的类型不会丢失,无需类似 XML 解析器;
序列化反序列化速度很快,不需要通过反射获取类型;
消息格式升级和兼容性不错,可以做到向后兼容。使用代码示例如下:
/** * * // IDl 文件格式 * synax = "proto3"; * option java_package = "com.test"; * option java_outer_classname = "StudentProtobuf"; * * message StudentMsg { * //序号 * int32 no = 1; * //姓名 * string name = 2; * } * */ StudentProtobuf.StudentMsg.Builder builder = StudentProtobuf.StudentMsg.newBuilder();builder.setNo(103);builder.setName("protobuf");//把student对象转化为byte数组StudentProtobuf.StudentMsg msg = builder.build();byte[] data = msg.toByteArray();//把刚才序列化出来的byte数组转化为student对象StudentProtobuf.StudentMsg deStudent = StudentProtobuf.StudentMsg.parseFrom(data);System.out.println(deStudent);
 
 
从性能和效率、空间开销、通用性和兼容性、安全性选择合适的RPC框架
notion image
RPC 框架在使用时要注意哪些问题?
  • 对象构建复杂
  • 对象过于庞大
  • 使用序列化框架不支持的入参
  • 对象有复杂的继承关系
 
 
什么是零拷贝?
系统内核处理IO分为两个操作
  1. 系统内核等待从网卡传输过来的数据,把数据写入到内核中
  1. 系统内核读取到数据后,拷贝进用户空间
 
notion image
原来 8 张图,就可以搞懂「零拷贝」了
作者 :小林coding 图解计算机基础网站: https://xiaolincoding.com/ 磁盘可以说是计算机系统最慢的硬件之一,读写速度相差内存 10 倍以上,所以针对优化磁盘的技术非常的多,比如零拷贝、直接 I/O、异步 I/O 等等,这些优化的目的就是为了提高系统的吞吐量,另外操作系统内核中的磁盘高速缓存区,可以有效的减少磁盘的访问次数。 这次,我们就以「文件传输」作为切入点,来分析 I/O 工作方式,以及如何优化传输文件的性能。 在没有 DMA 技术前,I/O 的过程是这样的: CPU 发出对应的指令给磁盘控制器,然后返回; 磁盘控制器收到指令后,于是就开始准备数据,会把数据放入到磁盘控制器的内部缓冲区中,然后产生一个中断; CPU 收到中断信号后,停下手头的工作,接着把磁盘控制器的缓冲区的数据一次一个字节地读进自己的寄存器,然后再把寄存器里的数据写入到内存,而在数据传输的期间 CPU 是无法执行其他任务的。 为了方便你理解,我画了一副图: 可以看到,整个数据的传输过程,都要需要 CPU 亲自参与搬运数据的过程,而且这个过程,CPU 是不能做其他事情的。 简单的搬运几个字符数据那没问题,但是如果我们用千兆网卡或者硬盘传输大量数据的时候,都用 CPU 来搬运的话,肯定忙不过来。 计算机科学家们发现了事情的严重性后,于是就发明了 DMA 技术,也就是 直接内存访问(Direct Memory Access) 技术。 什么是 DMA 技术?简单理解就是, 在进行 I/O 设备和内存的数据传输的时候,数据搬运的工作全部交给 DMA 控制器,而 CPU 不再参与任何与数据搬运相关的事情,这样 CPU