第02篇:手写JavaRPC框架之设计思路
天下代码一大抄, 抄来抄去有提高, 看你会抄不会抄!
# 一、前言
隔壁老李又在喷我了: "完犊子了,小编这绝对是为了骗粉丝,而水的一篇文章,到了第二篇竟然还没有开始写代码,又是一篇纯概念文章"。
我也想写代码,但是在没有讲清楚思路之前,一定不要上来就蛮干,不然就毫无设计可言了。小编向各位观众老爷保证,下一篇文章绝对上代码。
本篇文章非常重要,这是我们本系列文章中的重中之重,本篇文章的主要内容就是设计我们自己的通信协议及架构,可以这样说如果没有了本篇文章的内容,就不可能实现RPC。因为RPC的最基本要求就是能实现远程通信。本篇文章是讲述通信层的设计思路,下一篇就是实战的编写。(ps: 其实下一篇比较好写,因为代码我早就写完了嘻嘻,而这一篇竟然酝酿了一周还写的...不忍直视)
# 二、目标
本篇文章主要会围绕以下三方面展开叙述,希望在通读全篇后,大家都能在脑子中形成对这三个方面的认识,因为下面的三个方面是通信层搭建的主要指导思想。
- 设计我们自己的通信协议
- 确定我们的通信层的架构
- 确定我们的工程结构
# 2.1 为什么我们要设计自己的通信协议呢?
上一篇我们也说了,实现RPC可以基于http也可以基于tcp。他们各有各的好处,如果是基于http其实我们的挑战就相对比较小一些,因为实现http的协议已经是在太多了,我们只用通过代理进行层层封装即可,而我们之所以要自己实现通信协议就是。
作为Java程序猿还是要对底层通信协议的具体实现有点了解的。如果不了解的话也没关系,你只要知道他是二进制数据就可以了。我们一步一步通过代码编写将二进制数据转换成我们Java语言能够认识的数据就好了。如果这个过程你学会了,那么一通百通,http如何实现的其实大概也能知道猜到一点。
# 2.2 为什么要讲通信层的架构?
怎么理解架构?
作为一个有经验的开发者,都会清楚,我们写代码就如同写文章。好的代码一定是思路清晰的,思路清晰的代码耦合性一定是很少的。我们举一个例子,最近大环境不好,大厂裁员较多,很多小伙伴都要面试吧,就举一个面试的问题,通过这个例子来解释下什么是架构。
- 面试官说: 同学做一下自我介绍吧。
首先我们不能懵啊,如果懵了就说明没有头绪了,这样就容易讲乱,没有头绪在开发过程中的体现就是代码写的杂乱。比如你在介绍家乡的时候突然穿插了一下爱好,而在讲爱好的时候,又穿插的讲了一下家乡。这样就会导致主题不分明,听者会感觉会乱。所以这里我们就需要 单一职责。首先定义清楚你的讲话的结构,然后每个结构点就一个职责。
如下我们设计的面试架构是这些点:
姓名,家乡,大学,专业,兴趣爱好,单位职称 .
下面我们只用实现每个点的内容(主题清晰),最终将他组装成完成的自我介绍回答;
// 姓名,家乡,大学,专业,兴趣爱好,单位职称
public interface Introduce{
// 这是一个介绍类,负责介绍自己
public void introduce();
}
public class XiaoMing implements Introduce{
// 将任务进行拆分,拆分的维度是逻辑顺序,然后抽离出方法,抽离的维度是单一职责。
// 这样的好处是工能化,模块化,便于复用。
public void introduce(){
sout("我叫小明");
// 主题介绍家乡
introduceHometown();
// 主题介绍学校
introduceSchool();
// 主题介绍专业
introduceMajor();
// 主题介绍兴趣爱好
introduceInterest();
sout("从业xx年,目前在公司的职称是xxx");
}
private void introduceHometown(){
sout("我的老家是河南南阳")
sout("我的家乡就坐落在河南南阳邓州市")
sout("邓州市一个美丽的城市,是中国邓姓的发源地")
sout("邓州也是河南境内人口最多的一个县级城市")
}
private void introduceSchool(){
sout("我大学是在河南大学")
sout("河南大学简称河大,是一所位于中国河南省开封市涵盖文、史、哲、经、管、
法、理、工、医、农、教育、艺术等12个学科门类的省部共建型综合性公立大学。")
}
private void introduceMajor(){
sout("我的专业是计算机与信息工程")
}
private void introduceInterest(){
sout("我的个人爱好是写博客、打游戏、做美食、偶会也会跑跑步")
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
有没有发现单一职责的设计,会很大程度提高我们的代码利用率呢? 我们要的就是这个效果,所以我们再开始编码之前,最好提前定义清楚我们的架构是怎么样的。
上面的例子,不知道小编有没有给大家解释清楚,但是总归我们的目的,是要在设计的时候就要明确责任划分,尽可能的单一职责。尽可能的解耦。
要想设计一个好的框架,首先一定要有一个好的架构设计。这一点我们可以直接参考dubbo的设计架构。
上半部分不用看,我们只用看通信层就好了。
- serialize 序列化层,负责将二进制数据转成Java认识的数据类型
- transport 传输层,负责发送和接受数据
- exchange 转换成,通信层和业务逻辑层转换的地方。
- protocol 协议层,告诉serialize用什么协议来encode和decode数据的
所以说,天下代码一大抄,抄来抄去有提高,就看你会抄不会抄了。 我们的设计就主要参考dubbo来了。
# 2.3 工程结构设计
目前市面上的框架基本上都是自己来定制通信层,而通信层基本也不会单独的提供出去。但是本系列小编希望是通信层和业务能分开。通信层可以做RPC也可以利用通信层去实现消息队列或者是web容器。所以因为这个设计,就要求我们的项目能单独的去发布。所以我们整体的项目结构是由三个部分组成的,如下。
# 三、核心知识点
# 3.1 通信层协议定义
什么是协议呢?
其实就是规则,我们按照什么样的方式将二进制数据转换成Java对象。
如下图,我们的一条数据会分为4个部分
- 第一部分占用一个字节是协议标记,用来标记是http协议还是自定义协议。
- 第二部分占用一个字节是序列化标记,用来确定我们的真实报文使用什么来进行序列化和反序列化。
- 第三部分占用四个字节,用来表示数据的字节长度,确定真实报文的长度。
- 第四部分长度不固定,是真实的传输数据。最终会通过第二部分将这些二进制数据转换成Java对象。
以上就是我们定义的数据解析协议,通过上面的规则将二进制数据,转换成Java对象。
读到这里你有没有一点收获呢?
有没有发现,其实协议的概念,其实很简单,就是一个规则或者说是约定。能让彼此都互相认识的一个约定。
在本系列中,我们会自定义一个协议,同时也会兼容支持http协议。如果感兴趣,就跟着小编一起coding吧。
# 3.2 通信层架构设计
前面说了,我们是站在巨人的肩膀上的,根据dubbo的设计思路和我们的目标,我们也来画一张图。
我们的最终架构如上图。
包 | 作用 |
---|---|
serialize | 序列化协议层,包含了多种序列化协议 |
codec | 数据解码器和编码器的具体实现层 |
exchange | API交换层,业务层API和通信层API交换数据的地方,负责将业务数据转换成二进制数据发送,也负责将二进制数据转换成业务数据返回 |
model | 基础数据模型 |
business | 提供给开发者用来实现业务的api |
api | Fluent 风格的api, 这种风格的好处是不需要记住接下来的步骤和方法 |
# 3.3 工程结构
为了符合前面我们定的目标,所以我们要有一个大的工程。
项目名 | 职责 |
---|---|
mojito-net | 底层通信模块 |
mojito-rpc | rpc模块 |
mojito-spring-boot-starter | springboot自动化配置 |
由此我们的项目诞生了。下一篇我们就开始手撸代码吧。
.
├── README.md
├── mojito-net
│ ├── pom.xml
│ └── src
│ ├── main
│ │ ├── java
│ │ └── resources
│ └── test
│ └── java
├── mojito-rpc
│ ├── pom.xml
│ └── src
│ ├── main
│ │ ├── java
│ │ └── resources
│ └── test
│ └── java
├── mojito-spring-boot-starter
│ ├── pom.xml
│ └── src
│ ├── main
│ │ ├── java
│ │ └── resources
│ └── test
│ └── java
└── pom.xml
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28