【RPC】RPC的序列化方式

2023-11-09

序列化

网络传输的数据必须是二进制数据,但调用方请求的出入参数都是对象。对象是不能直接在网络中传输的,所以我们需要提前把它转成可传输的二进制,并且要求转换算法是可逆的,这个过程我们一般叫做“序列化”。这时,服务提供方就可以正确地从二进制数据中分割出不同的请求,同时根据请求类型和序列化类型,把二进制的消息体逆向还原成请求对象,这个过程我们称之为“反序列化”。

在一般的高级程序设计语言中,对象都是需要加载到内存中的。在没有进行持久化之前,是不能进行网络传输的。
序列化只是特殊的持久化的方式。遵循特定的序列化协议,接收方才能正确地还原发送方传递过来的对象。

image.png
在 RPC 的通信模型中,最关键的是要克服网络传输的问题。使得跨越网络也能达到调用的效果,所以在 RPC 框架中,我们一般遵循以下的 RPC 通信模型。
image.png

参考文档:深入理解RPC—序列化_xiaofang233的博客-CSDN博客_rpc 序列化

JDK 原生序列化方式

JDK 原生支持对象的序列化,主要依赖于 Serializable 序列化接口。

/**
* @author Real
* @since 2022-10-29 15:44
*/
public class Student implements Serializable{

    private static final long serialVersionUID = 1931653827252088076L;

    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
            "name='" + name + '\'' +
            ", age=" + age +
            '}';
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Student student = new Student();
        student.setName("Real");
        student.setAge(22);
        FileOutputStream studentFos = new FileOutputStream("student.dat");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(studentFos);
        // 将对象写入持久化文件
        objectOutputStream.writeObject(student);
        objectOutputStream.flush();
        objectOutputStream.close();
        // 将对象从持久化文件中读取出来
        FileInputStream fis = new FileInputStream("student.dat");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Student deStudent = (Student) ois.readObject();
        ois.close();
        System.out.println(deStudent);
    }

}

序列化具体的实现是由 ObjectOutputStream 完成的,而反序列化的具体实现是由 ObjectInputStream 完成的。
image.png

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

在序列化对象生成的文件中,包含一些无法识别的分隔符。
image.png
正是这些分隔符,反序列才能根据既定的序列化协议,将序列化文件读取到内存中。
实际上任何一种序列化框架,核心思想就是设计一种序列化协议,将对象的类型、属性类型、属性值一一按照固定的格式写到二进制字节流中来完成序列化,再按照固定的格式一一读出对象的类型、属性类型、属性值,通过这些信息重新创建出一个新的对象,来完成反序列化。

JSON

**JSON **是一种广泛用于前后端交互的一种数据格式,全称为 JavaScript Object Notation 。因为 JSON 是一种纯文本格式,且具有可读性,所以是一种非常适合用来传递信息的方式。JSON 的格式如下:

{
  "timestamp": 1667032472746,
  "status": 404,
  "error": "Not Found",
  "message": "No message available",
  "path": "/service/business/xxx"
}

JSON 虽然广泛用于前后端交互中,但是其性能并不好。

  • JSON 进行序列化的额外空间开销比较大,对于大数据量服务这意味着需要巨大的内存和磁盘开销;
  • JSON 没有类型,但像 Java 这种强类型语言,需要通过反射统一解决,所以性能不会太好。在前后端交互中,Java 通常需要依赖一些现有的 JSON 转换框架,比如 Jackson 、fastjson 等。

XML

XML ,可扩展标记语言 (Extensible Markup Language, XML)。类似于 JSON ,同样是一种可读的文本协议,格式如下:

<application android:label="@string/app_name" android:icon="@drawable/osg">
  <activity android:name=".osgViewer"
    android:label="@string/app_name" android:screenOrientation="landscape"> <!--  Force screen to landscape -->
    <intent-filter>
      <action android:name="android.intent.action.MAIN" />
      <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
  </activity>
</application>

XML 是由 <> 标记成对构成的。对于 XML 文件,占用的空间比 JSON 更大,效率比 JSON 更低,所以在对性能有要求的 RPC 框架中已经完全不使用 XML 文件了。
但是因为 XML 标签含有 Attribution 属性,可以指定标签对应的类型,与 Java 语言的强类型含义比较契合,所以常见于配置文件。

Hessian

Hessian 是一种动态类型、二进制、紧凑的,并且可跨语言移植的序列化框架。Hessian 协议要比 JDK、JSON 、XML 更加紧凑,性能上要比 JDK、JSON 、XML 序列化高效很多,而且生成的字节数也更小。
使用 Hessian 需要先引入对应的 Java 版本的依赖,使用上与 JDK 序列化方式相差不大。

public static void main(String[] args) throws IOException {
    Student student = new Student();
    student.setName("Real");
    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);
}

Hessian 虽然具有不错的序列化性能,但是在使用时也有需要注意的地方:

  • Linked 系列,LinkedHashMap、LinkedHashSet 等,但是可以通过扩展CollectionDeserializer 类修复;
  • Locale 类,可以通过扩展 ContextSerializerFactory 类修复;
  • Byte/Short 反序列化的时候变成 Integer。

Protobuf

参考文档:神奇的Google二进制编解码技术:Protobuf
浅谈protobuf

Protobuf 是 Google 公司内部的混合语言数据标准,是一种轻便、高效的结构化数据存储格式,可以用于结构化数据序列化,支持 Java、Python、C++、Go 等语言。Protobuf 使用的时候需要定义 IDL(Interface description language),然后使用不同语言的 IDL 编译器,生成序列化工具类,它的优点是:

  • 序列化后体积相比 JSON、Hessian 小很多;
  • IDL 能清晰地描述语义,所以足以帮助并保证应用程序之间的类型不会丢失,无需类似 XML 解析器;
  • 序列化反序列化速度很快,不需要通过反射获取类型;
  • 消息格式升级和兼容性不错,可以做到向后兼容。
// 消息定义
message Msg {
optional int32 id = 1;
}

// 实例化
Msg msg;
msg.set_id(43);

编写了 IDL 之后,我们就能以比 Hessian 、JSON 更高效的编码方式完成序列化。那么在 Protobuf 中这是如何实现的呢?使用变长方式编码数据
不使用固定长度来表示数字,而需要使用变长方法来表示。
规定:对于每一个字节来说,第一个比特位如果是1那么表示接下来的一个比特依然要用来解释为一个数字,如果第一个比特为0,那么说明接下来的一个字节不是用来表示该数字的。
对两个 8bit 即两位的数据进行编解码,过程如下:

    1010110000000010  
->  10101100 | 00000010 // 解析得到两个字节
    _          _
 
->  0101100  |  0000010  // 各自去掉最高位 
->  0000010  |  0101100  // 两个字节翻转顺序

    0000010  +  0101100
->  100101100           // 拼接 

最后我们得到了 100101100,这一串二进制表示数字 300。

300 的二进制表示是 100101100。如果用 int32 变量来存储,需要 4 个字节:100101100。但显然只需要 2 个字节即可。
1、每个字节的第一位,叫做 msb(most significant bit),用于标识下一个字节是否还属于这个整数(1:属于;0:不属于)。
2、从右到左(从低位到高位),每7位一段(留1位给msb),高位不足用0补齐,得到:10 0101100。
3、反转字节序(因为要网络字节序),得到:0101100 10。如果只是借鉴思想,用于数据压缩,可以不要这步。
4、填充msb,得到:0101100 0000010。即300在protobuf中的存储,只用了2个字节。
参考文档:protobuf可变长编码的实现原理

这种数字的变长表示方法在 protobuf 中被称之为 varint。但是对于有符号的数字,还是无法进行表示。可以有的思路是:
既然无符号数字可以方便的进行变长编码,那么我们将有符号数字映射称为无符号数字不就可以了,这就是所谓的 ZigZag 编码。

原始信息      编码后
0            0 
-1           1 
1            2
-2           3
2            4
-3           5
3            6

...          ...

2147483647   4294967294
-2147483648  4294967295

有无符号数的表示问题解决之后,接下来需要解决的就是数据类型和数据名称的问题。在 Protobuf 中,数据类型是有限的。

参考文档:protobuf 数据类型 - 梯子教程网

我们可以将每一种数据类型做一个 Type 的映射,每一种数据类型用固定的 bit 值来表示,这样在数据类型本身就非常有限的情况下,占用的空间是非常低的。
接下来,数据字段名称该怎么表示呢?
int key_name = 100;
那么我们真的需要把 “key_name” 这么多字符通过网络传递给对端吗?
既然通信双方需要协议,那么“key_name”这字段其实是 client 和 server 都知道的,它们唯一不知道的就是“哪些值属于哪些字段”。

这里的理解为:如果 server 定义了需要编号为 2 的数据,那么 client 需要传递 Key 的编号为 2 的数据。这样在 IDL 语言的帮助下,Client 会将符合数据类型的数据编码为 2 ,server 接收到即可得知目标数据。
多参数的情况,我们可以给参数列表定义一个固定的顺序来表示参数名称的编号。参数名称的编号,我们可以依赖于参数列表的 IDL 定义的顺序。

为解决这个问题,我们给每个字段都进行编号,比如通信双方都知道“key_name”这个字段的编号是 2,那么对于 int key_name = 100; 这个信息我们只需要传递:

  • 字段名称:2 (2 对应字段“key_name”)
  • 字段类型:0 (0 表示 varint 类型)
  • 字段值:100

所以无论用多么复杂的字段名称也不会影响编码后占据的空间,字段名称根本就不会出现在编码后的信息中。
**Protobuf **对于嵌套格式的数据,一样存在对应的数据处理方式。
image.png
在这样的组织格式下, IDL 语言也有对应的实现。

message SubMsg {
optional int32 id = 1;
}
message Msg {
optional SubMsg msg = 1;
}

protobuf 不只是一种序列化协议,也是一门语言。它可以将可读性较好的消息编码为二进制从而可以在网络中进行传播,而对端也可以将其解码回来。
在这里 protobuf 中定义的消息就好比 C 语言,编码后的二进制消息就好比机器指令。也可以类比于 Java 语言,编码之后就变成二进制的 .class 文件交由 JVM 执行。
总结
Protobuf 二进制编码之所以快,是因为它对编译原理以及信息的编解码有着十分优秀的实践使用方法。主要归纳为以下几点:

  • 使用变长编码 varint 表示数字。
    • 第一个比特位如果是 1 那么表示接下来的一个比特依然要用来解释为一个数字,如果第一个比特为0,那么说明接下来的一个字节不是用来表示该数字的。
  • 使用 ZigZag 编码将有符号数字映射为无符号数字。
    • 将有符号数字映射称为无符号数字解决数字的符号问题,这就是所谓的 ZigZag 编码。
  • 使用给字段名编号的方式解决字段名的传输问题。
    • 给每个字段都进行编号,避免在 C/S 双方单纯传递字段名,改为只传递字段名编号,加快编码效率。
  • 使用字段类型映射编码的方式解决字段的类型传输问题。
    • Protobuf 支持的数据类型是有限的,给每种数据类型加上编号,以后每次的调用中只需要传输编号就能知道字段类型。

其他的序列化协议

序列化协议有非常多,除了以上提到的常见的序列化协议,还有 Message pack、kryo 、flatbuf 等等。

flatbuf 的性能其实比 protobuf 的性能更高,但是因为复杂程度、社区维护等原因,业界内的使用程度和知名度并不如 protobuf 。
参考文档:浅谈protobuf

在选择序列化协议的时候,除了性能、通用性,还会有更多维度的考量。选择的时候遵循如下的权重是一个比较好的选择。
image.png
在做技术选型的时候,如果没有特殊要求,通常也会根据这个逻辑进行。

其他问题

  • 对象构造得过于复杂:属性很多,并且存在多层的嵌套。序列化框架在序列化与反序列化对象时,对象越复杂就越浪费性能,消耗 CPU,这会严重影响 RPC 框架整体的性能;另外,对象越复杂,在序列化与反序列化的过程中,出现问题的概率就越高。
  • 对象过于庞大:比如为一个大 List 或者大 Map,序列化之后字节长度达到了上兆字节。这种情况同样会严重地浪费了性能、CPU,并且序列化一个如此大的对象是很耗费时间的,这肯定会直接影响到请求的耗时。
  • 使用序列化框架不支持的类作为入参类:比如 Hessian 框架,天然是不支持LinkHashMap、LinkedHashSet 的,而且大多数情况下最好不要使用第三方集合类,如 Guava 中的集合类,很多开源的序列化框架都是优先支持编程语言原生的对象。因此如果入参是集合类,应尽量选用原生的、最为常用的集合类,如 HashMap、ArrayList。
  • 对象有复杂的继承关系:大多数序列化框架在序列化对象时都会将对象的属性一一进行序列化,当有继承关系时,会不停地寻找父类,遍历属性。就像问题 1 一样,对象关系越复杂,就越浪费性能,同时又很容易出现序列化上的问题。

在 RPC 框架的使用过程中,我们要尽量构建简单的对象作为入参和返回值对象,避免上述问题。

总结

这归根结底还是因为服务调用的稳定性与可靠性,要比服务的性能与响应耗时更加重要。另外对于 RPC 调用来说,整体调用上,最为耗时、最消耗性能的操作大多都是服务提供者执行业务逻辑的操作,这时序列化的开销对于服务整体的开销来说影响相对较小。
在使用 RPC 框架的过程中,我们构造入参、返回值对象,主要记住以下几点:

  • 对象要尽量简单,没有太多的依赖关系,属性不要太多,尽量高内聚;
  • 入参对象与返回值对象体积不要太大,更不要传太大的集合;
  • 尽量使用简单的、常用的、开发语言原生的对象,尤其是集合类;
  • 对象不要有复杂的继承关系,最好不要有父子类的情况。

实际上,虽然 RPC 框架可以让我们发起全程调用就像调用本地,但在 RPC 框架的传输过程中,入参与返回值的根本作用就是用来传递信息的,为了提高 RPC 调用整体的性能和稳定性,我们的入参与返回值对象要构造得尽量简单,这很重要。

远程过程调用,终归是远程调用,需要经过网络 I/O 的过程。
本地调用,才是在统一的语言环境内,使用语言内存体系构建的调用栈。
两者区别的是一个网络 I/O 的过程,所以 RPC 的关键就是在网络 I/O 的过程中发生的链路问题。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【RPC】RPC的序列化方式 的相关文章

  • 中断并标签,“标签 MyLabel 丢失”

    我有这样的代码 if condition1 break MyLabel while true some code here MyLabel if condition2 break more code here 我收到此错误 标签 MyLab
  • Java EE 6 和单例

    谁能解释一下在 Java EE 6 应用程序中实现 Singleton 的完整过程 我假设我不应该以声明静态变量的典型方式创建单例 而应该使用 Singleton注解 我必须这样做吗 难道只是声明一下的情况 Singleton就是这样 我还
  • 仅使用公钥在 HD 钱包中生成以太坊地址 (bitcoinj/web3j)

    我尝试为使用 bitcoinj 库实现的 HD 钱包密钥生成以太坊地址 但我感到困惑 DeterministicSeed seed new DeterministicSeed some seed code here null 1409478
  • 使用 JavaScript 与 Web 服务器通信的 Applet 是否可以迁移到 JWS?

    只是分享一些信息 希望对社区有用 由于各种浏览器停止支持插件 Applet 的可用性已经下降 Google 已决定停止对 NPAPI 插件的支持 EDGE 不支持插件 Firefox 也不鼓励使用插件 Mozilla 可能会跟进该套件 我们
  • Java:while循环冻结程序

    我正在制作一个游戏 我需要每 3 秒更新一次 JProgressBar 为此 我使用 while 循环 问题是我的程序由于 while 循环而冻结 我在其他问题中读到它 他们没有帮助我解决这个问题 我不知道如何解决 这是我的代码 publi
  • 解析 (yyyy-MM-dd) 格式的字符串日期

    我有一个 2013 09 18 形式的字符串 我想将其转换为 java util Date 我正在做这个 SimpleDateFormat sdf new SimpleDateFormat yyyy MM dd Date converted
  • Java 表达式树 [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 是否有相当于 net的 LINQ 下的表达式树JVM 我想实现一些类似 LINQ 的代码结构Scala
  • Java 客户端到服务器未知来源

    我有一个简单的乒乓球游戏 需要通过网络工作 服务器将创建一个带有球和 2 个球棒位置的游戏 当客户端连接到服务器时 服务器将创建一个名为 PongPlayerThread 的新类 它将处理客户端到服务器的输入和输出流 我的服务器工作100
  • SSLContext 初始化

    我正在看JSSE参考指南 我需要获取一个实例SSLContext为了创建一个SSLEngine 所以我可以使用它Netty以启用安全性 获取实例SSLContext I use SSLContext getInstance 我看到该方法被重
  • 如何在 Java 中使用 HTML 解析器和 Apache Tika 来提取所有 HTML 标签?

    我下载了 tika core 和 tika parser 库 但找不到将 HTML 文档解析为字符串的示例代码 我必须删除网页源的所有 html 标签 我能做些什么 如何使用 Apache Tika 进行编码 您想要 html 文件的纯文本
  • 自 JRE 1.7.0_25 起,Batik 无法进行转换

    自从我更新到 JAVA 1 7 0 25 以来 蜡染在应用转换时会抛出异常 堆栈跟踪是 java awt image ImagingOpException Unable to transform src image at java awt
  • Jersey 和 Spring 中的全局异常处理?

    我正在使用 Jersey 和 Spring 3 2 以及 Open CMIS 开发 RESTful Web 服务 我没有使用 Spring 的 MVC 模式 它只是 Spring IOC 和 Jersey SpringServlet 控制器
  • 为什么replaceAll在这行代码中不起作用? [复制]

    这个问题在这里已经有答案了 String weatherLocation weatherLoc 1 toString weatherLocation replaceAll how weatherLocation replaceAll wea
  • Java DNSLookup MX 记录列表。类似于 MXToolBox

    我正在构建一个程序来列出域的所有 MX 记录 起初似乎工作正常 但与在线工具进行比较后http mxtoolbox com http mxtoolbox com 有些域程序无法获取 MX 记录 而 MXToolbox 可以 我不确定原因是什
  • “强制更新快照/版本” - 这是什么意思

    在 Maven 项目中 选择 更新项目 时 有一个名为 强制更新快照 版本 的选项 它有什么作用 强制更新快照 版本 就像运行以下命令 mvn U install U 也可以用作 update snapshot 看here http boo
  • Web 服务客户端的 AXIS 与 JAX-WS

    我决定用Java 实现Web 服务客户端 我已经在 Eclipse 中生成了 Axis 客户端 并使用 wsimport 生成了 JAS WS 客户端 两种解决方案都有效 现在我必须选择一种来继续 在选择其中之一之前我应该 考虑什么 JAX
  • 如何从 Sublime Text 编辑器调试 Java 应用程序

    有时我正在对相当大的 Java 应用程序进行简单的修复 但我不想打开 Eclipse 来执行此任务 Eclipse 启动时间很长 并且由于该项目是由大量子项目构建的 而这些子项目无论如何都是由 Maven 构建的 因此需要很长时间才能使用
  • 在Java内存管理中,“PS”代表什么?

    每当我看到 Java 中对内存的引用时 各种空格总是以 PS 为前缀 PS 是什么意思 它开始困扰我 到目前为止我唯一的猜测是 泳池空间 但这将是多余的 例子 PS伊甸园空间 PS 幸存者空间 PS 终身空间 老一代 PS Perm Gen
  • java中什么是静态接口?

    我正在阅读Map Entry界面 当我注意到它是一个static界面 我不太明白什么是静态接口 它与常规接口有什么不同 public static interface Map Entry
  • 需要在没有wsdl的情况下调用soap ws

    我是网络服务的新手 这个网络服务是由 siebel 提供的 我需要调用一项网络服务 我的客户向我提供了以下详细信息 这是 SOAP 对于产品 请使用它作为端点 Request

随机推荐

  • 遗传算法基本介绍

    1 主要解决什么问题 是一种仿生全局优化算法 2 原理 思路是什么 选择 优胜劣汰 交叉 变异 一些重要概念 生物遗传概念在遗传算法中的对应关系 编码策略 常用的遗传算法编码方法主要有 二进制编码 浮点数编码等 可以证明 二进制编码比浮点数
  • 在html中加入网址,网页超链接怎么做,添加超链接网址的的详细步骤

    此系列教程主要讲解HTML从基础到精通 自己能够设计一个完整的前端网页项目 程序员写代码 在HTML中添加图片其实很简单 就是添加一个img的标签 图片标签的语法 一般有src alt width height四种属性就够用了 效果 图片的
  • 智能音箱借ChatGPT重获“新生”?

    曾经靠语音助手红极一时的智能音箱 近年来的市场表现却欠佳 据洛图科技发布的最新 中国智能音箱零售市场月度追踪 报告显示 2022年中国智能音箱总销量为2631万台 同比下降28 市场销售额为75 3亿元 同比下降25 而IDC发布的2023
  • 华为OD机试 - 太阳能板最大面积(Java)

    题目描述 给航天器一侧加装长方形或正方形的太阳能板 图中的红色斜线区域 需要先安装两个支柱 图中的黑色竖条 再在支柱的中间部分固定太阳能板 但航天器不同位置的支柱长度不同 太阳能板的安装面积受限于最短一侧的那根支柱长度 如图 现提供一组整形
  • 计算机中的打印机,如何添加打印机,教您电脑如何添加打印机

    打印机是我们工作中不可缺少的办公设备 那如果电脑上没安装打印机 可以进行打印吗 我们可以通过连接到同一网络上的打印机进行打印作业 那电脑怎样进行添加打印机呢 下面 小编给大家带来了电脑添加打印机的图文 打印机是现在我们办公设备的必要用品之一
  • 如何定位Release 版本中程序崩溃的位置 ---利用map文件 拦截windows崩溃函数

    1 案例描述 作为Windows程序员 平时最担心见到的事情可能就是程序发生了崩溃 异常 这时Windows会提示该程序执行了非法操作 即将关闭 请与您的供应商联系 呵呵 这句微软的 名言 恐怕是程序员最怕见也最常见的东西了 在一个大型软件
  • spring boot集成mybatis无法扫描mapper文件(坑)

    大半天耗在这上面 真的无语了 现象解决了 原因待查找 首先 如果你的spring boot集成mybatis项目报这个错 同时你使用的是YML的配置方式 再同时你用的是Intellij 那么就往下看吧 解决方法就是 使用这种配置方式 命名为
  • 马虎的算式

    标题 马虎的算式 小明是个急性子 上小学的时候经常把老师写在黑板上的题目抄错了 有一次 老师出的题目是 36 x 495 他却给抄成了 396 x 45 但结果却很戏剧性 他的答案竟然是对的 因为 36 495 396 45 17820 类
  • 刷题之反转字符串

    编写一个函数 其作用是将输入的字符串反转过来 输入字符串以字符数组 s 的形式给出 不要给另外的数组分配额外的空间 你必须原地修改输入数组 使用 O 1 的额外空间解决这一问题 示例 1 输入 s h e l l o 输出 o l l e
  • LAST_INSERT_ID使用造成订单串单问题

    订单串单问题 代码 String sql insert into this update sql List
  • 用动态数组实现了顺序表

    用动态数组实现了顺序表 作者 吕翔宇 e mail 630056108 qq com ALL RIGHTS RESERVED 版权所有 include
  • Spring Boot定时任务在分布式环境下的轻量级解决方案

    文章非原创 转载简书 原作者 foundwei 转载链接 https www jianshu com p 41970ba48453 Spring Boot提供了一个叫做Spring Task的任务调度工具 支持注解和配置文件形式 支持Cro
  • CGAL计算几何算法库安装和使用(一)

    CGAL是使用C 开发的计算几何算法库 提供Delaunay三角网 网格生成 多边形 以及各种几何处理算法 应用领域 计算机图形学 科学可视化 计算机辅助设计与建模 地理信息系统 分子生物学 医学影像学 机器人学和运动规划 和数值方法 1
  • KVM同步脏页原理

    文章目录 硬件基础 SPTE 硬件要素 工作流程 PML 硬件要素 工作流程 数据结构 用户态 内核态 API 脏页开启 脏页获取 流程 使能记录 记录脏页 流程图 具体过程 获取脏页 流程图 具体过程 实验 QEMU在内存迁移阶段首先会标
  • Struts2框架验证

    struts2有框架验证 我这边主要说的是XML配置的struts2的框架验证 继承validate方法的验证还是比较容易的 至于有人用注解来验证不怎么常见我也不讲了 感觉好多东西总结的都会有注解来掺和一脚 比如spring注入方式 最常见
  • Python全系列教程:超详细1小时学会Python,太简单了

    1 Hello world 安装完Python之后 打开IDLE Python GUI 该程序是Python语言解释器 你写的语句能够立即运行 我们写下一句著名的程序语句 并按回车 你就能看到这句被K R引入到程序世界的名言 在解释器中选择
  • UFT 自带两个经典sample航空订票应用程序

    大家知道UFT是QTP的升级换代产品 一定保留上一版系统原有的一些的痕迹 比如经典的Flights应用 安装路径 D Program Files x86 Micro Focus Unified Functional Testing samp
  • 单片机定时器/计数器、中断和串口控制位

    一 定时器 计数器 1 定时器控制寄存器 TCON TCON TF1 TR1 TF0 TR0 IE1 IT1 IE0 IT0 TF1 TF0 定时器 计数器中断请求标志位当定时器计数满溢出回零时 有硬件置位 并可申请中断 当CPU响应中断并
  • C语言:(含大量图解)你真的了解结构体吗?

    文章目录 结构体 结构体的定义 结构体大小计算 结构体的对齐规则 关于对齐规则的解释 为什么C语言要这样进行大小设定 平台移植原因 追求高性能 修改默认对齐数 结构体传参问题 结构体 结构体的定义 结构体是一些值的集合 这些值被称为成员变量
  • 【RPC】RPC的序列化方式

    序列化 网络传输的数据必须是二进制数据 但调用方请求的出入参数都是对象 对象是不能直接在网络中传输的 所以我们需要提前把它转成可传输的二进制 并且要求转换算法是可逆的 这个过程我们一般叫做 序列化 这时 服务提供方就可以正确地从二进制数据中