深度思考rpc框架面经系列之二

此篇总结是接上一篇做的,当然,独立看也是可以的,这几个系列讲解的是rpc框架的不同方面

4 序列化和反序列化(阿里一面)

4.1 为什么要序列化呢,解决什么问题呢?序列化和反序列化也是要消耗一定性能的

序列化是一个将对象的状态信息转换为可以存储或传输的形式的过程。反序列化则是将这种格式的数据再次转化回对象的过程。序列化和反序列化确实会消耗一定的性能,但是它们存在的理由是为了解决以下问题:

  1. 持久化: 为了将对象的状态信息永久地保存到磁盘上(例如,数据库、文件),通常需要将对象序列化。之后,可以从磁盘上读取这些信息,并通过反序列化将其恢复为对象。

  2. 远程调用 (RPC): 当在分布式系统中进行远程方法调用时,通常需要将对象序列化成一种格式,使其可以在网络上进行传输。接收方收到数据后再进行反序列化,恢复成原始的对象,然后进行处理。

  3. 数据交换: 当两个不同的应用或系统需要交换数据时,序列化可以提供一种共同的、结构化的格式(如JSON、XML)以方便数据交换。

  4. 缓存: 当使用像Redis这样的外部缓存时,通常需要序列化对象,以便将其存储在缓存中。当从缓存中检索数据时,再进行反序列化。

  5. 深拷贝: 通过序列化和随后的反序列化,可以创建对象的一个完全独立的拷贝。

虽然序列化和反序列化会带来一定的性能开销,但是为了满足上述需求,这些开销通常是可以接受的。然而,为了减少这些开销,通常有一些策略和最佳实践,例如选择高效的序列化格式、使用专门为速度优化的库(如Protocol Buffers、FlatBuffers等)等。

4.1.1 如果没有序列化就不能完成持久化,rpc,数据交换,缓存以及深拷贝这些操作吗,为什么

不完全是这样**。序列化是为这些操作提供了一种通用、结构化、和高效的方式。没有序列化,你仍然可以完成上述操作,但过程可能会变得复杂、低效或不可靠**。我们来详细解析:

  1. 持久化: 在不使用序列化的情况下,你可能需要为每个对象设计专门的保存和加载逻辑。例如,为每个对象属性定义数据库表的列。这样做是可行的,但会变得非常复杂,尤其是当对象结构经常变化时。

  2. 远程调用 (RPC): 不使用序列化,你需要为每个对象定义专门的编码和解码逻辑,以便在网络上传输。这不仅增加了复杂性,还可能引入错误,并导致效率低下的网络通信。

  3. 数据交换: 在没有序列化的情况下,两个不同语言系统之间的数据交换可能需要人为地定义和遵循特定格式。这会增加两个系统之间的紧耦合,从而降低灵活性和可维护性。

  4. 缓存: 外部缓存系统(如Redis)通常只能存储基本的数据类型,如字符串或字节数组。因此,要将一个对象存储到Redis中,你需要将其转换为这些基本数据类型,这本质上就是序列化。

  5. 深拷贝: 不使用序列化,你可能需要手动为每个对象实现深拷贝逻辑,这不仅费时,而且容易出错。

总之,虽然在没有序列化的情况下仍然可以完成这些操作,但序列化为我们提供了一种结构化、高效、可靠的方法来处理这些常见的任务。

(1)简单根本原因:对象、文件、数据,有许多不同的格式,很难统一网络传输和持久化保存。序列化相当于提供了一种统一的保存和传输格式,使得分布式系统的各个节点能以统一的方法访问序列化中存储的数据。

假设你有一个包含姓名和年龄的人物(Person)对象,如果没有序列化,你可能需要写代码来分别获取这个人物的姓名和年龄,然后以某种特定的格式(例如CSV或JSON)将它们存储或发送。然后,在需要的时候,你需要写代码来解析这个格式,然后使用解析的结果来创建一个新的人物对象。这种过程不仅需要写大量的代码,而且如果对象的结构发生改变(例如添加了新的字段),你可能需要修改你的代码,如果对象结构变化频繁,则代码修改也是很频繁的。

(2)其他:有的序列化协议可以在解码时保持较好的可读性,同时也能压缩数据,这样的情况下还能节省网络带宽

4.2 序列化和通信协议之间的关系

序列化和通信协议之间的关系主要在于序列化提供了在网络通信中发送和接收复杂对象的方法。在网络通信中,所有的数据最终都要被转换为字节流,然后才能通过网络发送。序列化就是这种转换的过程,它将对象的状态转换为字节流。通信协议则定义了如何发送和接收这些字节流。所以在很多网络通信的情况下,序列化是通信协议的一部分。例如,在HTTP协议中,我们经常使用JSON或XML作为序列化的方式来发送和接收数据。

4.3 假设有一个服务,它的入参是一个接口,这个接口下面有四个实现类,每个实现类有不同的字段,它们的特点是都是继承了同一个接口,基于这个场景,你的rpc框架需要用哪一种序列化方式,原因是什么?

我:能告诉我这个为什么涉及到序列化?

面试官:你觉得这个场景用json能work吗?因为你序列化的是一个接口,而不是具体的实现类

我:是不是可以在json中加一个字段呢,表示期望用的是哪一种实现类?

面试官:但是你加了字段之后,序列化和反序列化怎么进行,比如我刚开始序列化的对象中只有两个字段,后面又新增了几个字段,接收端怎么知道这变化的字段呢?

我:但是你用protocol buffer的话,就支持你自定义字段,然后可以这样顺利解析啊

面试官:原因是什么呢?为什么protocol buffer可以感知到新增或者减少的字段呢?

我:是因为protocol buffer的序列化是支持元数据自描述的,proto buffer中的一个字段的存储格由(type,length,value)决定的,这样的话,我总是能合理的切分每一个字段。比如说原来只有一个字段"k1":“v1”,现在新增一个字段"k10’":“v10”,那么第一个字段的存储格式是(string,2,k1),(string,2,v1);新增一个字段,数据的存储格式就是(string,2,k1),(string,2,v1),(string,3,k10),(string,3v10);;

gpt4正确答案:前向/后向兼容性:这意味着旧版本的序列化代码可以解析由新版本的代码生成的数据(前向),反之亦然(后向)。在Protocol Buffers中,这是通过为每个字段分配一个唯一的数字标识符并保持这些标识符的一致性来实现的;此外就涉及到前面提到的存储格式的问题了,通过长度字段可以知道这个新增字段id的值,key和value;这也是为什么Protocol Buffers可以感知到新增或者减少的字段的原因。只要标识符不变,字段可以被重命名、添加或删除,而不破坏兼容性。

面试官:json里面也是支持元数据描述的,只是需要特殊设置一下;如果你没开启的话,那你每个字段就变成了字符串了,然后单独去json了对吧。在有类的情况下,json有一个字符表名这个类的全称是什么,反序列化的时候会根据类的名称去找特定的实现类。 你刚刚说的那种是序列化的时候本身会一用个描述元数据的文档,在整个二进制里面就不需要重组二进制信息

4.4 序列化实现的深拷贝和我们重写java的clone方法实现的深拷贝有什么不同?

使用序列化来实现深拷贝与使用clone方法有几个关键的区别:

  1. 实现方式:

    • 序列化:通过将对象转化为字节流,然后再从字节流中重新构建一个新对象,从而实现深拷贝。
    • clone方法:需要为对象实现Cloneable接口,并重写clone方法。当你调用clone方法时,会创建一个新对象,并手动复制原始对象的属性到新对象。
  2. 深度:

    • 序列化:自动地为对象及其所有嵌套对象实现深拷贝,不需要额外的工作。
    • clone方法:默认的clone方法是浅拷贝。要实现深拷贝,你需要手动为每个嵌套对象调用clone方法。这可能变得非常复杂,特别是对于具有多个层次的对象。
  3. 性能:

    • 序列化:由于涉及到对象与字节流之间的转换,使用序列化实现深拷贝的性能开销比较大。
    • clone方法:通常来说,使用clone方法实现的深拷贝性能更好,因为它直接在内存中操作。
  4. 灵活性与安全性:

    • 序列化:不需要为每个对象都写拷贝逻辑,但所有涉及的对象都必须实现Serializable接口。此外,使用序列化进行深拷贝可能会暴露对象的私有字段,从而带来安全风险。
    • clone方法:允许你为每个对象定制深拷贝的逻辑,但需要为每个对象都写拷贝逻辑,这增加了实现的复杂性
  5. 异常处理:

    • 序列化:序列化和反序列化过程中可能会抛出异常,如IOExceptionClassNotFoundException
    • clone方法clone方法可能会抛出CloneNotSupportedException,但只有在对象没有实现Cloneable接口时才会这样。
  6. 外部资源:

    • 序列化:序列化并不会拷贝对象的外部资源,如文件句柄、数据库连接等。
    • clone方法:同样,clone方法也不会拷贝外部资源。

总的来说,使用序列化实现深拷贝是一种简单而自动的方法,但可能会牺牲性能。而使用clone方法实现深拷贝虽然更高效,但可能需要更多的手工工作和维护。选择哪种方法取决于具体的应用场景和需求。

5 jdk的序列化接口接口-Serializable(用友一面)

5.1 这个接口是用来做什么的?

Serializable 是 Java 提供的一个标记接口(即没有任何方法的接口),它告诉 Java 虚拟机 (JVM) 一个类的对象可以被序列化。序列化是将对象的状态信息转化为可以存储或传输的形式的过程。这个形式通常是字节流,但也可以是其他格式。当对象被序列化后,可以将其写入到文件、发送到网络等,然后在适当的时候再反序列化回对象。

序列化主要用于以下场合:

  1. 持久化数据:对象的状态可以被存储在磁盘上,之后再恢复。
  2. 远程方法调用 (RMI):对象可以在网络上进行传输,然后在另一侧反序列化,以便在远程系统上使用。
  3. 分布式计算:对象可以在集群或网格中的不同节点间传输。

简而言之,Serializable 接口使得一个类的对象可以被转换为字节流,从而可以进行存储、传输,并在之后再转换回原始对象状态。

5.2 我们在spring中经常会将对象实例序列化成一个json哈,那这个时候并不需要实现这个接口对吧?

是的,你是对的。在 Spring 或许多其他框架中,当我们将对象实例序列化为 JSON 时,我们通常不需要让该对象实现 Serializable 接口。这是因为 JSON 序列化通常是通过反射来访问对象的字段并将它们转化为 JSON 字符串的,而不是通过 Java 的内置序列化机制。

Serializable 接口主要用于 Java 原生的序列化机制,特别是当你需要将对象状态保存到文件或通过网络在 Java 应用之间发送对象时。这个机制涉及到将对象转化为一个特定的字节流格式,该格式可以随后再反序列化回对象。

但在现代应用中,JSON 已经成为了一种非常流行的数据交换格式,特别是在 web 应用和 RESTful 服务中。为了序列化和反序列化 JSON,我们通常会使用如 Jackson、Gson 等库,这些库并不要求对象实现 Serializable 接口。

5.3 如何使用这个序列化接口

当你想通过Java原生的序列化机制序列化一个对象时,该对象的类必须实现Serializable接口。这是一个标记接口,也就是说它没有任何方法需要实现,但是它告诉JVM这个对象是可以序列化的。

以下是一个简单的Java示例,展示了如何使用Serializable接口进行序列化和反序列化:

import java.io.*;class Person implements Serializable {private static final long serialVersionUID = 1L;String name;int age;Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Person [name=" + name + ", age=" + age + "]";}
}public class SerializationDemo {public static void main(String[] args) {// 对象序列化try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {Person person = new Person("John", 25);oos.writeObject(person);System.out.println("Person object has been serialized.");} catch (IOException e) {e.printStackTrace();}// 对象反序列化try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {Person deserializedPerson = (Person) ois.readObject();System.out.println("Deserialized Person: " + deserializedPerson);} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}}
}

5.4 这个接口里有一个id,你知道这个id是干什么的嘛?

它是serialVersionUID, 是一个私有的静态常量,用于表示序列化版本。这是可选的,但建议总是包含它,以确保序列化兼容性。

注意事项:

  1. serialVersionUID

是一个私有的静态常量,用于表示序列化版本。这是可选的,但建议总是包含它,以确保序列化兼容性。

  1. 如果类的字段发生改变(例如添加新字段),可能需要更改

serialVersionUID。如果你没有设置serialVersionUID并且更改了类的结构,那么在尝试反序列化旧的对象时,可能会收到InvalidClassException。

  1. 不是所有的Java对象都可以被序列化。对象必须是可序列化的,并且它引用的所有对象也都必须是可序列化的。如果对象包含不能序列化的字段,你可以将该字段标记为transient,这样它就不会被序列化。使用ObjectOutputStream来序列化对象,并将其写入文件。使用ObjectInputStream从文件读取并反序列化对象。

5.4.1 为什么需要这么一个serialVersionUID字段?

答:因为接收方反序列化发送方发送的字节流时,需要有一个对象来对接,从字节流中解析出的所有字段必须在对接的对象在全部存在,否则会造成数据不一致。这里也就是说接收发送双方的序列化和反序列化的对象的版本必须一致。比如发送方在第一次发送Person对象的字节流时,这个对象只有name字段,接收方的对接对象也只有name字段,接收和发送双方使用的对象版本号都是1,所以第一次接收方反序列化成功,随后发送方往Person中新增了一个age字段,并且版本号置为2,但是接收方的对接Person并没有新增这个字段并且版本号还是1,那么发送方第二次发送的字节流,接收方会抛异常。

5.4.2 所以如果发送方新增了一个字段,将serialVersionUID自增,同时也需要将接收端的这个对应接收类添加相应的字段并且自增相应的版本嘛,所以在双方的版本和字段都同步更新的情况下,一定能反序列化成功,对嘛?

是的,你理解得很准确。当你手动管理serialVersionUID时,当发送方的类结构发生变化(例如,添加或删除字段),你需要确保:更新serialVersionUID。保证发送方和接收方使用的类定义是同步的。只要双方的类定义(包括字段和serialVersionUID)保持一致,就可以成功进行序列化和反序列化。但需要注意的是,仅仅同步serialVersionUID是不够的。例如,如果发送方增加了一个字段并更新了serialVersionUID,而接收方仅更新了serialVersionUID而没有添加相应的字段,那么在反序列化时仍然会出错。
因此,手动管理serialVersionUID和确保类的同步都是为了保证序列化的兼容性。当你有完全的控制权,并可以确保两边都同步更新时,这种方式很有效。但在大型分布式系统中,可能需要更多的策略和工具来管理这种兼容性。

5.4.3 如果接收方和发送方的序列化的原始类能做到同步更新保持一致,这个serialVersionUID应该能够允许去掉把?

理论上,如果你确保两端的类结构始终是一致的,那么serialVersionUID可以省略,因为JVM会为没有定义serialVersionUID的类生成一个默认的版本号。这个默认的版本号是基于类的结构(包括成员变量、方法等)计算出来的。
但实际上,这样做存在风险:
隐式行为:省略serialVersionUID意味着JVM会为你的类生成默认的版本号。当类的任何细节发生变化时,这个版本号也会发生变化。这种隐式的行为可能会导致难以预见的反序列化问题。
跨JVM版本的不确定性:不同的JVM实现(或版本)可能会产生不同的默认serialVersionUID值,这可能会导致在不同的JVM版本之间进行序列化/反序列化时出现问题。
减少出错的机会:手动设置serialVersionUID意味着你已经考虑过版本兼容性问题。当类的结构发生改变时,有意识地更改或保持serialVersionUID值可以减少意外的序列化问题。
所以,即使你能确保发送方和接收方的类结构始终保持一致,为了减少潜在的序列化问题,最佳实践还是推荐为你的可序列化类显式地定义serialVersionUID。

5.5 分布式环境下,一般是如何做到管理这个对象的版本的呢?

在分布式环境下,对象的版本管理变得尤为重要,因为服务间通信、数据交换和状态共享是分布式系统的基础。下面是一些建议和最佳实践,以确保对象版本在分布式环境中得到适当的管理:

  1. 中央化仓库:使用中央化的仓库(如 Maven Central、Nexus、Artifactory 等)来存储和管理所有的JAR包和库。这确保了在分布式环境中,所有服务都引用的是相同的库版本。

  2. 契约驱动的设计 (Contract-Driven Design):在微服务环境中,你可以使用工具(如Spring Cloud Contract)来定义并验证服务间的交互。这确保了服务间的接口和数据格式的一致性,而不需要每个服务都更新到最新版本。

  3. 使用数据模式管理:对于如 Apache Kafka、Apache Avro 这样的系统,你可以使用 Confluent Schema Registry 或 Apache Avro 的内置模式版本控制来管理数据结构的变化。

  4. 向后兼容:尽量使新版本的对象向后兼容,这样即使服务版本不一致,它们仍然可以正常交互。

  5. 版本命名约定:遵循一致的版本命名约定,例如语义版本控制(Semantic Versioning),这样你可以通过版本号轻松地了解更改的性质。

  6. 弃用策略:如果你需要移除或更改对象的某个部分,提供一个过渡期,并在此期间支持旧版本。这给予其他服务足够的时间来进行必要的调整。

  7. 服务发现与注册:使用服务注册与发现机制(如Eureka、Consul等),这样服务可以知道其他服务的版本,并据此做出决策。

  8. 监控与警告:使用监控工具来跟踪分布式环境中的版本变化。如果检测到不一致的版本,立即发出警告。

  9. 灰度部署与金丝雀发布:在引入新版本的服务或对象时,不要立即在所有实例上部署。先在一小部分实例上部署,确保其与其他服务的兼容性,然后再逐渐扩大部署范围。

  10. 维护文档:持续更新文档,记录每个版本的更改和不同版本之间的差异。

在分布式环境中,版本管理是一个持续的、需要多方面关注的过程。与团队合作,制定策略,并使用工具来自动化流程,是确保成功的关键。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/37270.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VScode如何设置中文教程

前言:打开VSCode软件,可以看到刚刚安装的VSCode软件默认使用的是英文语言环境,但网上都是vscode中文界面教你怎么设置中文,可能不利于小白阅读,所以重装vscode,手摸手从英文变成中文。 设置为中文 打开VS…

CDH6.3.2搭建HIVE ON TEZ

参考 https://blog.csdn.net/ly8951677/article/details/124152987 ----配置hive运行引擎 在/etc/hive/conf/hive-site.xml中修改如下: hive.execution.engine mr–>tez hive.execution.engine 设为tez或者运行代码的时候: set hive.execution.eng…

android app控制ros机器人五(百度地图)

半吊子改安卓,新增了标签页,此标签页需要显示百度地图 按照官方教程注册信息,得到访问应用AK,步骤也可以参照下面csdn Android地图SDK | 百度地图API SDK 【Android】实现百度地图显示_宾有为的博客-CSDN博客 本人使用的是aar开…

区分多个鼠标的滚轮滚动消息

实现功能:电脑插了两个或多个鼠标,程序中需要区分不同鼠标的滚轮滚动消息。 实现方式:直接上代码,复制粘贴可用,具体处理逻辑可以根据实际需求进行修改和扩展,请注意,这段代码是在Windows操作系…

Python 图形界面框架TkInter(第八篇:理解pack布局)

前言 tkinter图形用户界面框架提供了3种布局方式,分别是 1、pack 2、grid 3、place 介绍下pack布局方式,这是我们最常用的布局方式,理解了pack布局,绝大多数需求都能满足。 第一次使用pack() import …

学习笔记整理-DOM-02-事件监听

一、什么是"事件监听" DOM允许书写JavaScript代码以让HTML元素对事件作出反应什么是"事件": 用户与网页的交互动作当用户点击元素时当鼠标移动到元素上时当文本框的内容被改变时当键盘在文本框中被按下时当网页已加载完毕时… “监听”,顾名思义…

开学季电容笔怎么选?iPad第三方电容笔了解下

不少的学生党开学必备清单里都少不了电容笔,可见其的重要性。自从苹果发布了ipad的原装电容笔以来,这款电容笔在目前市面上就一直很受欢迎,不过由于Apple Pencil的售价实在是太贵了,使得大部分人都买不起。于是,市面上…

leetcode做题笔记78子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 思路一:回溯 void backtracking(int* nums, int numsSize, int** res, int* ret…

在 Linux 虚拟机上使用 Azure 自定义脚本扩展版本

参考 azure创建虚拟机,创建虚拟机注意入站端口规则开放80端口、 2.转到资源,点击扩展应用程序,创建存储账户,创建容器,上传文件,选择文件,会自动执行部署。 apt-get update -y && apt-get insta…

ROS订阅相机图像消息,并将图像保存为视频帧

需求 需要编写一个Python程序,订阅电脑外接的深度相机发出的视频消息,录制视频并逐帧保存为图片到本地,用于采集制作数据集的图片信息 运行环境 Ubuntu18.04 ROS Melodic Python2.7 Python程序 #!/usr/bin/env python # -*- coding: u…

Vue-day03 组件

1.组件机制 组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展HTML元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用is特性进行了扩展的原生 HTML…

chatgpt和xmind结合起来帮你制作精美的思维导图

介绍 chatgpt和xmind结合起来帮你制作精美的思维导图。 1.输出Markdown格式 2.xmind导入.md文件

web集群学习:nginx+keepalived实现负载均衡高可用性

目录 项目架构 一,环境介绍 二,项目部署 在Web服务器上配置Web测试页面 nginx负载均衡配置 配置Nginx_Master 通过vrrp_script实现对集群资源的监控(1>通过killall命令探测服务运行状态) 通过vrrp_script实现对集群资源…

div输入框的文字超过指定行数用省略号表示css

实现效果:超过四行用省略号表示 实现方法: .text{overflow: hidden;text-overflow: ellipsis;display: -webkit-box;-webkit-line-clamp: 4; // 自定义行数-webkit-box-orient: vertical; }

Go和Java实现外观模式

Go和Java实现外观模式 下面我们通过一个构造各种形状的案例来说明外观模式的使用。 1、外观模式 外观模式隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型 模式,它向现有的系统添加一个接口&#xff…

【设计模式】代理模式

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。 在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。 介绍 意图:为其他对象提供一种代理以…

【面试问题】事务中执行了异步任务分发数据,由于事务未提交,导致异步任务无法执行

文章目录 问题描述:解决办法: 问题描述: OverrideTransactional(rollbackFor Exception.class)public ServiceResponse ctsqCallbackProcess(OaFlowRecord params) {// 查询任务单数据// 更新任务单信息// 异步分发数据到CRMS系统}客户数据分…

TX Text Control .NET Server for ASP.NET Crack

TX Text Control .NET Server for ASP.NET Crack TX Text Control.NET Server for ASP.NET是用于Web应用程序或服务的服务器端组件。它是一个完全可编程的ASP.NET文字处理引擎,提供了广泛的文字处理功能。使用TX Text Control.NET Server,程序员可以开发…

react组件化开发详解

React是一个流行的JavaScript库,用于构建用户界面,并且以组件化的方式进行开发。下面将详解React组件化开发的概念和步骤: 组件化思维: 组件化开发是将复杂的用户界面划分为独立、可重用的小部件(组件)。…

【833. 字符串中的查找与替换】

来源:力扣(LeetCode) 描述: 你会得到一个字符串 s (索引从 0 开始),你必须对它执行 k 个替换操作。替换操作以三个长度均为 k 的并行数组给出:indices, sources, targets。 要完成第 i 个替换操作: 检查…