阿里一面灵魂一问:RPC或者HTTP什么时候需要序列化和反序列化?

本文涉及的产品
应用实时监控服务-应用监控,每月50GB免费额度
云原生网关 MSE Higress,422元/月
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 大家好,我是热心网友 —— 小林。有位读者问了,我这么一个问题:

大家好,我是热心网友 —— 小林。

有位读者问了,我这么一个问题:

不管是 RPC 或者 HTTP,只要传输的内容是「对象」,要想在接收方还原出一摸一样的「对象」,那就需要序列化和反序列化。

那什么是序列化和反序列化呢?

RPC 能帮助我们的应用透明地完成远程调用,即调用其他服务器的函数就像调用本地方法一样。发起调用请求的那一方叫做调用方,被调用的一方叫做服务提供方。

调用方和服务提供方一般是不同的服务器,所以就需要通过网络来传输数据,并且 RPC 常用于业务系统之间的数据交互,需要保证其可靠性,所以 RPC 一般默认采用 TCP 协议来传输。同时, HTTP 协议也是建立在 TCP 之上的。

网络传输的数据必须是二进制数据,但调用方请求的出入参数都是对象,而对象是肯定没法直接在网络中传输的,需要提前把「对象转成二进制数据」进行网络传输,这个转换过程就做序列化。相反,服务提供方收到网络数据后,需要将「二进制数据转成对象」,这个转换过程就叫做反序列化。

总结来说,序列化就是将对象转换成二进制数据的过程,以方便传输或存储。而反序列就是将二进制转换为对象的过程。

为什么 RPC 经常提到序列化呢?

因为网络传输的数据必须是二进制数据,所以在 RPC 调用中,对入参对象与返回值对象进行序列化与反序列化是一个必须的过程。

HTTP 什么时候需要序列化呢?

举个例子。

客户端和服务端交互的数据是什么 JSON,这时候发送方需要将 JSON 将对象转换成二进制数据发送到网络,接收方需要将接收到的二进制数据转换成 JSON 对象。

说了,这么多概念,接下来跟大家说说有哪些常用的序列化方式?

JDK 原生序列化

Java 语言中 JDK 就自带有序列化的方式,举个 JDK 序列化的例子。

注意,这个例子是将 Student 对象保存到文件,保存到文件的数据是二进制数据,所以并不是说序列化只用在网络传输场景里,只要是保存数据的场景只能是二进制数据时,就需要用序列化。

import java.io.*;
public class Student implements Serializable {
    //学号
    private int no;
    //姓名
    private String name;
    public int getNo() {
        return no;
    }
    public void setNo(int no) {
        this.no = no;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Student{" +
                "no=" + no +
                ", name='" + name + '\'' +
                '}';
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        String basePath = "/root";
        FileOutputStream fos = new FileOutputStream(basePath + "student.dat");
        //初始化一个学生对象
        Student student = new Student();
        student.setNo(1);
        student.setName("xiaolin");
        //将学生对象序列化到文件里
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(student);
        oos.flush();
        oos.close();
        //读取文件中的二进制数据,并将数据反序列化为学生对象
        FileInputStream fis = new FileInputStream(basePath + "student.dat");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Student deStudent = (Student) ois.readObject();
        ois.close();
        System.out.println(deStudent);
    }
}

从上面的代码,我们可以知道:

  • JDK 自带的序列化具体的实现是由 ObjectOutputStream 完成的;
  • 而反序列化的具体实现是由 ObjectInputStream 完成的。

既然序列化是将对象转换为二进制数据,那序列化的过程的二进制数据肯定是有某种固定的格式。

比如 JDK 自带的序列化的过程如下图:

序列化过程就是在读取对象数据的时候,不断加入一些特殊分隔符,这些特殊分隔符用于在反序列化过程中截断信息。

  • 头部数据用来声明序列化协议、序列化版本,用于高低版本向后兼容;
  • 对象数据主要包括类名、签名、属性名、属性类型及属性值,当然还有开头结尾等数据,除了属性值属于真正的对象值,其他都是为了反序列化用的元数据
  • 存在对象引用、继承的情况下,就是递归遍历“写对象”逻辑

所以,从这个例子我们可以知道,任何一种序列化的方式,其核心思想就要设计一套将对象转换成某种特定格式的二进制数据,接着反序列化的时候,就能根据规则从二进制数据解析出对象。

这么看,序列化其实就是一种协议,序列化方和反序列化方都要遵循相同的规则,否则就无法得到正常的数据。

不过,JDK 原生序列化缺陷就是不能跨语言,只能在 Java 生态里使用。

JSON

JSON 数据的格式相信大家都很熟悉了吧,在 Web 应用里特别常见,通常后端和前端的耦合就是 JSON 数据。

JSON 是典型的 Key-Value 方式,没有数据类型。很多语言都实现了 JSON 序列化的第三库,所以 JSON 序列化的方式可以跨语言。

比如,Java 语言中常用的 JSON 第三方类库有:

  • Gson: 谷歌开发的 JSON 库克,功能十分全面。
  • FastJson: 阿里巴巴开发的 JSON 库克,性能十分优秀。
  • Jackson: 社区十分活跃且更新速度很快。

我这里说个 C++ 的 JSON 第三方库:JSON for Modern C++。

使用起来很方便,仅需要包含一个头文件“json.hpp”,没有外部依赖,也不需要额外的安装、编译、链接工作,适合快速上手开发。

因为 JSON 是 Key-Value 形式,所以 JSON for Modern C++ 的操作和标准容器 map 一样,用关联数组的“[]”来添加任意数据。

这里贴几个例子:

// 给 JSON for Modern C++ 的 json 类取个别名
using json_t = nlohmann::json;  
// JSON对象
json_t j;                                   
// "age":18
j["age"] = 18;
// "name":"xiaolin"
j["name"] = "xiaolin";    
// "gear":{"suits":"2099"}
j["gear"]["suits"] = "2099"; 
// "jobs":["superhero"]  
j["jobs"] = {"superhero"};                  
vector<int> v = {1,2,3};  
// "numbers":[1,2,3]
j["numbers"] = v;                          
map<string, int> m =                       
    {{"one", 1}, {"two", 2}};    
// "kv":{"one":1,"two":2}
j["kv"] = m;

添加完 JSON 数据完成后,就可以调用成员函数 dump() 进行初始化,得到的 JSON 文本形式,也就是 JSON 字符串:

cout << j.dump() << endl;

反序列化也很简单,只要调用静态成员函数 parse() 就行,直接得到 JSON 对象:

// JSON文本,原始字符串
string jsonStr = R"({               
    "name": "xiaolin",
    "age" : 18
})";
// 从字符串反序列化
json_t j = json_t::parse(jsonStr);    
// 验证序列化是否正确
assert(j["age"] == 18);        
assert(j["name"] == "xiaolin");

对于通常的应用来说,掌握了基本的序列化和反序列化就够用了,如果想要了解 JSON for Modern C++ 其他特性,可以去看它的 Github。

JSON 进行序列化存在的问题,因为 JSON 进行序列化的额外空间开销比较大,对于大数据量服务这意味着需要巨大的内存和磁盘开销,所以如果在传输数据量比较小的场景,就可以采用 JSON 序列化的方式。

ProtoBuffer

ProtoBuf 是由 Google 出品的,是一种轻便、高效的结构化数据存储格式,可以用于结构化数据序列化,支持 Java、Python、C++、Go 等语言。

Protobuf 使用的时候必须写一个 IDL(Interface description language)文件,在里面定义好数据结构,只有预先定义了的数据结构,才能被序列化和反序列化。

下面是一个简单的 IDl 文件格式:

syntax = "proto2";  // 使用第2版
package sample;   // 定义名字空间
message Person {            // 定义消息
  required string name = 1;  // required表示必须字段
  required int32 id = 2;
  optional string email = 3;  // optional字段可以没有
}

写完 IDL 文件后,然后使用不同语言的 IDL 编译器,生成序列化工具类。

Protobuf 在 Github 有文档介绍了不同语言是怎么使用编译器生成序列化工具类了,我在这里就不介绍了。

这里贴 C++ 和 Java 语言使用 Protobuf 相关接口进行序列化和反序列化的例子。

C++ 进行序列化和反序列化的例子:

// 类型别名
using person_t = sample::Person;        
// 声明一个Protobuf对象
person_t p;                             
// 设置每个字段的值 
p.set_id(1);                           
p.set_name("xiaolin");
p.set_email("xiaolincoding@163.com");
// 序列化到字符串 
string enc;
p.SerializeToString(&enc);
// 反序列化
person_t p2; 
p2.ParseFromString(enc);

Java 进行序列化和反序列化的例子:

/**
 *
 * // 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(1);
builder.setName("xiaolin");
//把student对象转化为byte数组
StudentProtobuf.StudentMsg msg = builder.build();
byte[] data = msg.toByteArray();
//把刚才序列化出来的byte数组转化为student对象
StudentProtobuf.StudentMsg deStudent = StudentProtobuf.StudentMsg.parseFrom(data);
System.out.println(deStudent);

现在很多大厂都在使用 Protobuf,而且 gRPC 就是基于 Protobuf 来做的序列化。


总结

将对象保存到文件,或者通过网络传输给对方,都是需要将对象转换二进制数据才能完成,那么对象转二进制数据的过程就是序列化,相反的,二进制数据转对象的过程就是反序列化的过程。

我也给你介绍了几种常见的序列化方式:

  • JDK 自带的序列化方式无法跨语言使用;
  • JSON 序列化大多数语言都支持,JSON 优势是使用起来简单,容易阅读,应用广泛,缺点就是不适合大数据量的场景;
  • ProtoBuf 使用需要定义 IDL 文件,序列化后体积相比 JSON 小很多,现在很多大公司都在用,gRPC 框架使用 protobuf 序列化。

本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。

相关文章
|
7月前
|
缓存 安全 API
RPC vs. HTTP:谁主沉浮在网络通信的江湖?
RPC vs. HTTP:谁主沉浮在网络通信的江湖?
945 0
|
4月前
|
负载均衡 网络协议 小程序
SpringCloud远程调用为啥要采用HTTP,而不是RPC?
【8月更文挑战第28天】在微服务架构日益盛行的今天,SpringCloud凭借其强大的生态系统和灵活的集成能力,成为了众多企业构建微服务系统的首选框架。在微服务之间的远程调用中,一个常见的问题是选择HTTP还是RPC(远程过程调用)作为通信协议。本文将深入探讨SpringCloud为何更倾向于采用HTTP而非RPC进行远程调用。
362 5
|
16天前
|
Dubbo 安全 应用服务中间件
Apache Dubbo 正式发布 HTTP/3 版本 RPC 协议,弱网效率提升 6 倍
在 Apache Dubbo 3.3.0 版本之后,官方推出了全新升级的 Triple X 协议,全面支持 HTTP/1、HTTP/2 和 HTTP/3 协议。本文将围绕 Triple 协议对 HTTP/3 的支持进行详细阐述,包括其设计目标、实际应用案例、性能测试结果以及源码架构分析等内容。
|
2月前
|
负载均衡 Java 开发者
Spring Cloud 远程调用:为何选择 HTTP 而非 RPC?
【10月更文挑战第1天】在微服务架构中,远程服务调用是一个核心环节。面对HTTP和RPC(Remote Procedure Call,远程过程调用)这两种通信协议,Spring Cloud 选择了HTTP作为其主要通信手段。本文将深入探讨Spring Cloud选择HTTP而非RPC的原因,以及这一选择在实际工作中的优势。
100 0
|
2月前
|
负载均衡 API 数据格式
RPC和HTTP的区别?
RPC和HTTP的区别?
109 0
|
4月前
|
前端开发 C# 开发者
WPF开发者必读:MVVM模式实战,轻松构建可维护的应用程序,让你的代码更上一层楼!
【8月更文挑战第31天】在WPF应用程序开发中,MVVM(Model-View-ViewModel)模式通过分离关注点,提高了代码的可维护性和可扩展性。本文详细介绍了MVVM模式的三个核心组件:Model(数据模型)、View(用户界面)和ViewModel(处理数据绑定与逻辑),并通过示例代码展示了如何在WPF项目中实现MVVM模式。通过这种模式,开发者可以更高效地构建桌面应用程序。希望本文能帮助你在WPF开发中更好地应用MVVM模式。
205 1
|
4月前
|
API 开发者 微服务
RPC和 HTTP协议
【8月更文挑战第8天】RPC(远程过程调用)使程序能像本地调用般请求远程服务,简化网络通信细节。其优点包括高效的数据传输及严格的类型定义,适合微服务间的高效通信。HTTP(超文本传输协议)则是用于万维网数据传输的通用协议,以文本为基础,易于理解和调试,并被广泛支持。两者各有侧重,RPC偏高速服务通信,HTTP则更适用于多样化的网络场景。选择时需根据具体需求决定。
|
4月前
|
XML 存储 JSON
(十二)探索高性能通信与RPC框架基石:Json、ProtoBuf、Hessian序列化详解
如今这个分布式风靡的时代,网络通信技术,是每位技术人员必须掌握的技能,因为无论是哪种分布式技术,都离不开心跳、选举、节点感知、数据同步……等机制,而究其根本,这些技术的本质都是网络间的数据交互。正因如此,想要构建一个高性能的分布式组件/系统,不得不思考一个问题:怎么才能让数据传输的速度更快?
106 1
|
4月前
|
网络协议 编译器 Go
揭秘!TCP、RPC、gRPC、HTTP大PK,谁才是网络通信界的超级巨星?一篇文章带你秒懂!
【8月更文挑战第25天】本文以教程形式深入对比了TCP、RPC、gRPC与HTTP这四种关键通信协议,并通过Go语言中的示例代码展示了各自的实现方法。TCP作为一种可靠的传输层协议,确保了数据的完整性和顺序性;RPC与gRPC作为远程过程调用框架,特别适合于分布式系统的函数调用与数据交换,其中gRPC在性能和跨语言支持方面表现出色;HTTP则是广泛应用于Web浏览器与服务器通信的应用层协议。选择合适的协议需根据具体需求综合考量。
299 0
|
5月前
|
网络协议 Dubbo Java
什么是RPC?RPC和HTTP对比?RPC有什么缺点?市面上常用的RPC框架?
选择合适的RPC框架和通信协议,对于构建高效、稳定的分布式系统至关重要。开发者需要根据自己的业务需求和系统架构,综合考虑各种因素,做出适宜的技术选型。
494 1