Dubbo分层设计之Serialize层

news/2024/11/28 7:46:38/

前言

Dubbo 框架采用 微内核 + 插件 的基本设计原则,自身功能几乎也都通过 SPI 扩展点实现,可以方便地被用户自由扩展和更换。
Dubbo 框架采用分层设计,自上而下共分为十层,各层均为单向依赖,每一层都可以剥离上层被复用。
本篇文章就来介绍一下最底下的 Serialize 序列化层。

理解序列化

序列化 (Serialization) 是将对象的状态信息转换为可以存储或传输的字节序列的过程,与之对应的还有反序列化,即将可存储或传输的字节序列转换为对象的过程。

为什么需要序列化呢???
以 Java 语言为例,它是一门面向对象的编程语言,我们在程序里操作的是对象,方法调用的参数和返回值亦是对象。Dubbo 作为一个 RPC 框架,基于代理帮我们实现了远程调用,屏蔽了底层实现的细节。但是我们得知道,对象本身是无法在网络中传输的,网络传输的只能是字节序列。
所以,Dubbo 在发送请求前,先得把对象序列化成字节序列,对端收到字节序列后再按照同样的规则反序列化成对象,再交给我们的业务代码处理,发送响应结果同样如此。

设计实现

Dubbo 针对 Serialize 层专门新建了一个dubbo-serialization 模块,其中子模块dubbo-serialization-api 用于定义抽象接口,其它模块负责具体实现。

dubbo-serialization
--dubbo-serialization-api
--dubbo-serialization-hessian2
--dubbo-serialization-fastjson
--dubbo-serialization-kryo
--dubbo-serialization-fst
--dubbo-serialization-jdk
--dubbo-serialization-protostuff
--dubbo-serialization-avro
......

Serialize 层核心接口就三个:

  • Serialization:序列化接口,用于生成序列化器和反序列化器
  • ObjectOutput:对象序列化器接口
  • ObjectInput:对象反序列化器接口

Serialization 是扩展接口,用于生成具体的序列化器和反序列化器,默认使用 hessian2。

@SPI("hessian2")
public interface Serialization {byte getContentTypeId();String getContentType();@AdaptiveObjectOutput serialize(URL url, OutputStream output) throws IOException;@AdaptiveObjectInput deserialize(URL url, InputStream input) throws IOException;
}

ObjectOutput 是序列化器的抽象接口,用于往输出流写对象。它继承自 DataOutput,除了可以写 Object,还支持写 Java 基本类型和字节数组。

public interface ObjectOutput extends DataOutput {void writeObject(Object obj) throws IOException;/****下面是Dubbo协议特有的需求,用来写异常、事件、和Map,其它协议未必支持****/default void writeThrowable(Object obj) throws IOException {writeObject(obj);}default void writeEvent(Object data) throws IOException {writeObject(data);}default void writeAttachments(Map<String, Object> attachments) throws IOException {writeObject(attachments);}
}

ObjectInput 是反序列化器的抽象接口,用于从输入流读对象,代码就不贴了。

看到这里,你可能会有一个疑问。
RPC 请求和响应都被封装成 Request、Response 对象了,为什么不只定义一个writeObjectreadObject 方法直接读写整个 Request/Response 对象呢,为什么还要单独提供读写 int、boolean、String 等基本类型的方法???
这是因为,Dubbo 协议的报文包含两部分:协议头部、请求体。序列化只管请求体部分的数据,对于协议头部它是不关心的。而 Request/Response 对象属性是同时包含这两部分的数据的,如果直接序列化整个对象传输,会造成带宽的浪费,传输冗余的数据。
所以 Dubbo 的编解码器DubboCodec 不是一股脑直接读写整个大对象的,而是按照顺序写多个对象,对端再按照相同的顺序读出来就好了。

Dubbo 对 Request 编码的方法是DubboCodec#encodeRequestData ,对于一次 Dubbo 协议的 RPC 调用,Consumer 要依次写入:

  1. version(String)Dubbo协议的版本号
  2. serviceName(String)服务名
  3. version(String)服务的版本号
  4. methodName(String)方法名
  5. parameterTypesDesc(String)参数类型描述
  6. args(Object…)方法参数
  7. attachments(Map)隐式参数
@Override
protected void encodeRequestData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {RpcInvocation inv = (RpcInvocation) data;out.writeUTF(version);// https://github.com/apache/dubbo/issues/6138String serviceName = inv.getAttachment(INTERFACE_KEY);if (serviceName == null) {serviceName = inv.getAttachment(PATH_KEY);}out.writeUTF(serviceName);out.writeUTF(inv.getAttachment(VERSION_KEY));out.writeUTF(inv.getMethodName());out.writeUTF(inv.getParameterTypesDesc());Object[] args = inv.getArguments();if (args != null) {for (int i = 0; i < args.length; i++) {out.writeObject(encodeInvocationArgument(channel, inv, i));}}out.writeAttachments(inv.getObjectAttachments());
}

Provider 再从输入流里按照相同的顺序读出来即可,代码是DecodeableRpcInvocation#decode

自定义序列化

Serialization 被设计成 SPI 接口,所以它可以很轻松的被替换。
接下来,我们就基于 Fastjson2 写一个序列化模块,替换掉默认的 hessian2,让你对整个序列化过程理解的更加清楚。

首先,我们新建一个dubbo-extension-serialization-fastjson2 模块。因为我们要依赖 Dubbo 提供的接口去实现一套新的序列化组件,所以自然要引入dubbo-serialization-api 模块。又因为我们是基于 fastjson2 实现的,所以也得引入 fastjson2 的依赖。

<dependencies><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-serialization-api</artifactId><version>${dubbo.version}</version></dependency><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.44</version></dependency>
</dependencies>

新建 Fastjson2Serialization 类实现 Serialization 接口,很简单,返回我们实现的 Fastjson2 对应的序列化器即可。

public class Fastjson2Serialization implements Serialization {@Overridepublic byte getContentTypeId() {return 22;}@Overridepublic String getContentType() {return "text/json";}@Overridepublic ObjectOutput serialize(URL url, OutputStream output) throws IOException {return new Fastjson2ObjectOutput(output);}@Overridepublic ObjectInput deserialize(URL url, InputStream input) throws IOException {return new Fastjson2ObjectInput(input);}
}

再编写 Fastjson2ObjectOutput 实现 ObjectOutput 接口,实现序列化相关的逻辑。我们这里直接用 JSONB 格式传输,效率比 JSON 字符串更高。

public class Fastjson2ObjectOutput implements ObjectOutput {private final OutputStream output;public Fastjson2ObjectOutput(OutputStream output) {this.output = output;}@Overridepublic void writeObject(Object obj) throws IOException {JSONWriter jsonWriter = JSONWriter.ofJSONB();jsonWriter.writeAny(obj);jsonWriter.flushTo(output);}......省略一堆方法
}

序列化器有了,再就是 Fastjson2ObjectInput 反序列化器了,同样的,基于 JSONReader 读即可。

public class Fastjson2ObjectInput implements ObjectInput {private final InputStream input;private final JSONReader jsonReader;public Fastjson2ObjectInput(InputStream input) {this.input = input;try {byte[] bytes = new byte[input.available()];input.read(bytes);this.jsonReader = JSONReader.ofJSONB(bytes);} catch (IOException e) {throw new RuntimeException(e);}}@Overridepublic Object readObject() throws IOException, ClassNotFoundException {return jsonReader.readAny();}......省略一堆方法
}

至此,我们自定义的基于 Fastjson2 的序列化器就实现好了。

我们可以写个单元测试,看看它是否可以正常工作:

@Test
public void test() throws Exception {Fastjson2Serialization serialization = new Fastjson2Serialization();ByteArrayOutputStream outputStream = new ByteArrayOutputStream();ObjectOutput objectOutput = serialization.serialize(null, outputStream);objectOutput.writeUTF("xixi");objectOutput.writeInt(100);objectOutput.writeFloat(200.5F);objectOutput.flushBuffer();InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());ObjectInput objectInput = serialization.deserialize(null, inputStream);Assertions.assertEquals("xixi", objectInput.readUTF());Assertions.assertEquals(100, objectInput.readInt());Assertions.assertEquals(200.5F, objectInput.readFloat());
}

接下来的问题是,如何让 Dubbo 加载我们自己写的序列化器呢???
这就要利用到 Dubbo 的 SPI 机制了,Dubbo 启动时会扫描 ClassPath 下所有的META-INF/dubbo 目录下,以类的全限定名命名的文件,然后读取文件内容,以 Key-Value 的形式加载扩展接口的实现类。

所以,我们也在模块里新建META-INF/dubbo/org.apache.dubbo.common.serialize.Serialization 文件,内容写上:

fastjson2=dubbo.extension.serialization.fastjson2.Fastjson2Serialization

这样 Dubbo 就会去加载我们的序列化实现了。

最后一步,就是让 Provider 和 Consumer 用上我们自定义的序列化器。Provider 可以在 ProtocolConfig 里指定:

ProtocolConfig protocolConfig = new ProtocolConfig("dubbo", 20880);
protocolConfig.setSerialization("fastjson2");ServiceConfig.setProtocol(protocolConfig);

Consumer 可以在 ReferenceConfig 里设置参数来指定:

Map<String, String> parameters = new HashMap<>();
parameters.put("serialization", "fastjson2");ReferenceConfig.setParameters(parameters);

接下来你就可以启动 Provider 和 Consumer,发起一次 RPC 调用,看看是否用的你自定义的序列化器。

尾巴

Dubbo 框架设计自上而下分了十层,最底层就是序列化层,它提供了 Java 对象到字节序列互相转换的能力,以方便参数和返回值可以在网络中传输。核心是利用 Serialization 接口用来生成序列化器和反序列化器,Dubbo 官方内置了很多序列化实现,开发者也可以自己实现一个实例化器,再基于 SPI 机制方便的替换掉默认实现。


http://www.ppmy.cn/news/1324167.html

相关文章

css3 纯代码案例

css3 纯代码案例 前言渐变之美1.1 纯CSS3实现的渐变背景1.2 使用多重颜色和方向打造丰富渐变效果1.3 渐变色停留动画的巧妙运用 纯CSS图形绘制2.1 使用border属性制作三角形、梯形等形状伪类箭头图标2.2 利用transform创建旋转、缩放的图形 浮动的阴影敲代码css准备reset 样式复…

LaWGPT安装和使用教程的复现版本【细节满满】

文章目录 前言一、下载和部署1.1 下载1.2 环境安装1.3 模型推理 总结 前言 LaWGPT 是一系列基于中文法律知识的开源大语言模型。该系列模型在通用中文基座模型&#xff08;如 Chinese-LLaMA、ChatGLM等&#xff09;的基础上扩充法律领域专有词表、大规模中文法律语料预训练&am…

【JavaEE进阶】 图书管理系统开发日记——壹

文章目录 &#x1f332;序言&#x1f334;前端代码的引入&#x1f38b;约定前后端交互接口&#x1f343;后端服务器代码实现&#x1f6a9;UserController.java&#x1f6a9;BookController.java ⭕总结 &#x1f332;序言 该图书管理系统&#xff0c;博主将一步一步进行实现。…

Microsoft Word 删除空行

Microsoft Word 删除空行 1. 删除空行1.1. 替换1.2. 段落标记 References 1. 删除空行 1.1. 替换 1.2. 段落标记 特殊格式 -> 段落标记 References [1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/

220v变5vic-220v变直流5v小封装220MA电流

220v变5v ic-220v变直流5v小封装220MA电流&#xff0c;交流或者直流输入都可以&#xff0c;交流输入的时候输入端需要先整流&#xff08;半波、全波都可以&#xff09;&#xff0c;40v~265v输入&#xff0c;经过220v变5v ic芯片电路&#xff0c;稳定输出5v200MA电流&#xff0c…

压力测试+接口测试(工具jmeter)

jmeter是apache公司基于java开发的一款开源压力测试工具&#xff0c;体积小&#xff0c;功能全&#xff0c;使用方便&#xff0c;是一个比较轻量级的测试工具&#xff0c;使用起来非常简单。因 为jmeter是java开发的&#xff0c;所以运行的时候必须先要安装jdk才可以。jmeter是…

Vue3新特性defineModel()便捷的双向绑定数据

官网介绍 传送门 配置 要求&#xff1a; 版本&#xff1a; vue > 3.4(必须&#xff01;&#xff01;&#xff01;)配置&#xff1a;vite.config.js 使用场景和案例 使用场景&#xff1a;父子组件的数据双向绑定&#xff0c;不用emit和props的繁重代码 具体案例 代码实…

wins安装paddle框架

一、安装 https://www.paddlepaddle.org.cn/install/quick?docurl/documentation/docs/zh/install/pip/windows-pip.html 装包&#xff08;python 的版本是否满足要求&#xff1a; 3.8/3.9/3.10/3.11/3.12&#xff0c; pip 版本为 20.2.2 或更高版本 &#xff09; CPU 版:…