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框架?
- HTTP请求除了请求数据还包含了数据包,以及换行、回车符等
- HTTP属于无状态协议,客户端无法对请求和响应进行关联
- 每次请求都需要重新建立连接,响应完成后再关闭连接。对于高性能的RPC来说,性能很慢满足
RPC框架
- TCP连接
- 通过序列化的二进制数据进行传输
- 需要设计一个可扩展且向后兼容的传输协议
序列化问题
Java类什么情况下需要实现序列化?
当需要转换成二进制字节流时,而像Feign这种调用入参出参是不需要序列化的。
JSON 进行序列化有这样两个问题,你需要格外注意:
- JSON 进行序列化的额外空间开销比较大,对于大数据量服务这意味着需要巨大的内存和磁盘开销;
- 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框架

RPC 框架在使用时要注意哪些问题?
- 对象构建复杂
- 对象过于庞大
- 使用序列化框架不支持的入参
- 对象有复杂的继承关系
什么是零拷贝?
系统内核处理IO分为两个操作
- 系统内核等待从网卡传输过来的数据,把数据写入到内核中
- 系统内核读取到数据后,拷贝进用户空间
