Dubbo分层设计之Serialize层

前言

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.mzph.cn/news/627397.shtml

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

相关文章

GO——单元测试(test)

go test用来做什么 做单元测试&#xff0c;测试函数是否符合预期 go test在哪个包 testing 如何使用 参考&#xff1a; https://geektutu.com/post/quick-go-test.html 以my_func.go中的Add方法为例 在同一个文件夹下添加my_func_test.go文件 测试文件以_test.go为结尾里…

远程视频会议卡顿!如何改善企业网络连接质量?

您的企业是否有这样的组网挑战&#xff1f; 要将不同分公司/店铺的监控画面汇总到服务器或者平台系统上&#xff0c;却由于地理位置过于分散&#xff0c;而且监控部署环境复杂多样&#xff0c;不同分公司/店铺部署的网络也不一样&#xff0c;有些甚至还是家用网络&#xff0c;…

现在00后开发人员不晓得加班为何事嘛?

我招了两个做HTML5开端开发的人员&#xff0c;是从培训机构招来的&#xff0c;按理说他们应该很努学很用样才对的。他们上班第一天我就跟他们讲&#xff0c;我们不需要上、下班打卡&#xff1b;你们也不必太过担心迟到或早退。因为我们搞开发的人员首先是按自己的工作任务完成情…

【部署LLaMa到自己的Linux服务器】

部署LLaMa到自己的Linux服务器 1、Llama2 项目获取方法1&#xff1a;有git可以直接克隆到本地方法2&#xff1a;直接下载 2、LLama2 项目部署3、申请Llama2许可4、下载模型权重5、运行 1、Llama2 项目获取 方法1&#xff1a;有git可以直接克隆到本地 创建一个空文件夹然后鼠标…

蓝牙网关G602

一、产品概述 G602是一款支持蓝牙4.2/5.0的蓝牙网关&#xff0c;主处理器采用580MHz的MIPS24KEc处理器&#xff0c;DRAM为DDR2 64MB&#xff0c;16MB FLASH。G602蓝牙网关集成PA和LNA&#xff0c;蓝牙扫描和连接距离可以达到100米以上&#xff0c;极大的增加了覆盖范围&#x…

CORS漏洞学习

CORS漏洞属于一个协议漏洞&#xff0c;具体是由于同源策略的设置问题触发的漏洞&#xff0c;漏洞利用条件较为苛刻&#xff0c;但实战中也常见。 首先要了解同源策略 什么是同源策略&#xff1f; 同源策略是一种Web浏览器安全机制&#xff0c;旨在防止网站相互攻击。 同源策…

LeetCode刷题——394. 字符串解码(HOT100)

✊✊✊&#x1f308;大家好&#xff01;本篇文章将较详细介绍栈的题目394. 字符串解码&#xff0c;提供栈和递归两种解法。代码语言为&#xff1a;C代码&#x1f607;。 &#x1f3a1;导航小助手&#x1f3a1; 394. 字符串解码&#x1f512;1、题目&#xff1a;☀️2、思路&…

数学建模-Matlab R2022a安装步骤

软件介绍 MATLAB是一款商业数学软件&#xff0c;用于算法开发、数据可视化、数据分析以及数值计算的高级技术计算语言和交互式环境&#xff0c;主要包括MATLAB和Simulink两大部分&#xff0c;可以进行矩阵运算、绘制函数和数据、实现算法、创建用户界面、连接其他编程语言的程…

2024年【危险化学品经营单位主要负责人】考试报名及危险化学品经营单位主要负责人考试资料

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 危险化学品经营单位主要负责人考试报名考前必练&#xff01;安全生产模拟考试一点通每个月更新危险化学品经营单位主要负责人考试资料题目及答案&#xff01;多做几遍&#xff0c;其实通过危险化学品经营单位主要负责…

NeRF 其三:Instant-NGP

NeRF 其三&#xff1a;Instant-NGP 1. 球谐函数1.1 NeRF 中球谐函数的作用1.2 球谐函数1.2.1 当阶数 j 0 j0 j0 时&#xff0c; m 0 m0 m0&#xff1a;1.2.2 当阶数 j 1 j1 j1 时&#xff0c; m 0 m0 m0&#xff1a;1.2.3 当阶数 j 1 j1 j1 时&#xff0c; m 1 m1 m1&…

SSL弱加密算法的漏洞研究

文章目录 一、什么是 SSL二、SSL/TLS 协议作用三、SSL/TLS 协议的基本思路四、如何保证公钥不被篡改?五、SSLSCAN工具1、下载和安装2、使用六、免责声明一、什么是 SSL SSL 代表安全套接字层。它是一种用于加密和验证应用程序(如浏览器)和 Web 服务器之间发送的数据的协议。…

vue中设置注释模板

参考地址 ctrlshiftp 打开编辑器配置输入configure user snippets - 选择 new global snipp files - 命名为 vueComment&#xff0c;弹出注释模板&#xff0c;即可自定义注释 如下/// 回车 即可在代码块中使用注释 { "Print to console": {"prefix": &q…

什么是游戏盾,游戏盾是如何做到免疫攻击的

什么是游戏盾&#xff1a;游戏盾是针对游戏行业面对的DDoS、CC攻击推出的针对性的网络安全解决方案&#xff0c;相比高防IP&#xff0c;除了能针对大型DDoS攻击&#xff08;T级别&#xff09;进行有效防御外&#xff0c;还具备彻底解决游戏行业特有的TCP协议的CC攻击问题能力&a…

拖拽不够自由?Vue3 DnD它来了!

前言 众所周知&#xff0c;在React中有一款非常强大的拖拽库&#xff0c;叫React DnD&#xff0c;而Vue中&#xff0c;大部分都是类似于vue.draggable等拖拽排序的库&#xff0c;然而它并不能满足我们所有的需求&#xff0c;特别是应对一些自由拖拽或混合拖拽的场景(例如&…

OpenHarmony之HDF驱动开发流程指导

开发指导 场景介绍 关于驱动的开发我们主要目的是实现驱动代码的编写&#xff0c;但是驱动开发过程中需要服务管理、消息机制管理&#xff0c;才能使驱动在代码编译过程中进行加载。以下开发步骤中介绍了驱动开发、驱动消息机制管理开发、驱动服务管理开发的步骤。 驱动开发…

henauOJ 1104: 单词数

题目描述 lily的好朋友xiaoou333最近很空&#xff0c;他想了一件没有什么意义的事情&#xff0c;就是统计一篇文章的单词总数。下面你的任务是帮助xiaoou333解决这个问题。 输入 有多组数据&#xff0c;每组一行&#xff0c;每组就是一篇小文章。每篇小文章都是由小写字母和…

spring boot mybatis-plus dynamic-datasource 配置文件 相关依赖环境配置

spring boot mybatis-plus dynamic-datasource 配置文件 相关依赖环境配置 ##yaml配置 server:port: 8866servlet:context-path: /yymtomcat:max-threads: 300connection-timeout: 57000max-connections: 500connection-timeout: 57000 spring:datasource:dynamic:primary: m…

二叉树遍历C++

假设二叉树上各结点的权值互不相同且都为正整数。 给定二叉树的后序遍历和中序遍历&#xff0c;请你输出二叉树的前序遍历的最后一个数字。 输入格式 第一行包含整数 N&#xff0c;表示二叉树结点总数。 第二行给出二叉树的后序遍历序列。 第三行给出二叉树的中序遍历序列。 …

【IPC通信--共享内存mmap】

共享内存是一种高效的进程间通信方式&#xff0c;可以在多个进程之间共享数据&#xff0c;提高程序的效率。mmap是一种常用的实现共享内存的机制&#xff0c;它可以将一个文件或者设备映射到内存中&#xff0c;使得多个进程可以通过访问这块内存来实现数据共享。 一、共享内存…

SpringBoot 源码解析4:refresh 方法解析

SpringBoot 源码解析4&#xff1a;refresh 方法解析 1. refresh 方法解析2. 准备刷新 AbstractApplicationContext#prepareRefresh3. 获取bean工厂 AbstractApplicationContext#obtainFreshBeanFactory4. 准备bean工厂 AbstractApplicationContext#prepareBeanFactory5. Servle…