目录
[2. 服务端 模块划分](#2. 服务端 模块划分)
[2.1 Network模块](#2.1 Network模块)
[2.2 Protocol模块](#2.2 Protocol模块)
[2.3 Dispatcher模块](#2.3 Dispatcher模块)
[2.4 RpcRouter模块](#2.4 RpcRouter模块)
[2.5 Publish-Subscribe模块](#2.5 Publish-Subscribe模块)
[2.6 Registry-Discovery模块](#2.6 Registry-Discovery模块)
[2.7 Server](#2.7 Server)
第三方库关于 future 的讲解部分,还没有整理完...之后整理出来了,再单独发叭,我们先来看一下对于项目的设计部分~
项目设计
1.理解项目功能
(1)
- 本质上来讲,我们要实现的rpc(远端调用)思想上并不复杂,甚至可以说是简单
- 其实就是客户端想要完成某个任务的处理,但是这个处理的过程并不自己来完成,而是将请求发送到服务器上,让服务器来帮其完成处理过程,并返回结果,客户端拿到结果后返回。
- 然而上图的模型中,是⼀种 多对⼀或⼀对⼀的关系
- ⼀旦服务端掉线,则客户端⽆法进行远端调用,且其服务端的负载也会较高
- 因此在rpc实现中,我们不仅要实现其基本功能,还要再进⼀步,实现分布式架构的rpc。
- 分布式架构: 简单理解就是 由多个节点组成的⼀个系统,这些节点通常指的是服务器,将不同的业务或者同⼀个业务 拆分分布在不同的节点上,通过协同⼯作解决高并发的问题,提高系统扩展性和可用性。
(2)
- 其实现思想也并不复杂,也就是 在原来的模型基础上,增加⼀个注册中心,基于注册中心不同的服务提供服务器向注册中心进行服务注册
- 相当于告诉注册中心自己能够提供什么服务,而客户端在进行远端调用前,先通过注册中心进行服务发现,找到能够提供服务的服务器,然后发起调用。
(3)
而其次的发布订阅功能,则是依托于 多个客户端围绕服务端进行消息的转发。
不过单纯的消息转发功能,并不能满足于大部分场景的需要,因此会在其基础上实现基于 主题订阅的转发。
(4)基于以上功能的合并,我们可以得到⼀个实现所有功能的结构图:
- 在上图的结构中,我们甚至可以 让每⼀个Server作为备用注册中心形成分布式架构,⼀旦⼀个注册中心下线,可以向备用中心进行注册以及请求
- 且在此基础上客⼾端在请求Rpc服务的时候,因为可以有多个rpc-provider可选,因此可以实现简单的负载均衡策略
- 且基于注册中心可以更简便实现发布订阅的功能。
(5)项目的三个主要功能:
- rpc调用。
- 服务的注册/发现/下线/上线通知。
- 主题消息的发布 订阅。
2. 服务端 模块划分
(1)服务端的功能需求:
- 基于网络通信接收客户端的请求,提供 rpc服务。
- 基于网络通信接收客户端的请求,提供 服务注册与发现,上线&下线通知。
- 基于网络通信接收客户端的请求,提供 主题操作(创建/删除/订阅/取消),消息发布。
(2)在服务端的模块划分中,基于以上理解的功能,可以划分出这么几个模块:
- Network:网络通信模块。
- Protocol:应用层通信协议模块。
- Dispatcher:消息分发处理模块。
- RpcRouter:远端调用路由功能模块。
- Publish-Subscribe:发布订阅功能模块。
- Registry-Discovery:服务注册/发现/上线/下线功能模块。
- Server:基于以上模块整合而出的服务端模块。
2.1 Network模块
该模块为网络通信模块,实现底层的网络通信功能,这个模块本质上也是⼀个比较复杂庞大的模块,因此鉴于项目的庞大,该模块我们将使用 陈硕老师的Muduo库来进行搭建~
2.2 Protocol模块
(1)
应用层通信协议模块的存在意义: 解析数据,解决通信中有可能存在的粘包问题,能够获取到⼀条完整的消息。
- 关于协议的制定和粘包问题,博主在前文有写到过~
- [Linux#62][TCP] 首位长度:封装与分用 | 序号:可靠性原理 | 滑动窗口:流量控制
- 在前边的muduo库基本使用中,我们能够知道想要让⼀个服务端/客户端对消息处理,就要设置⼀个onMessage的回调函数,在这个函数中对收到的数据进行应用层协议处理。
而Protocol模块就是是网络通信协议模块的设计,也就是在网络通信中,我们++必须设计⼀个应用层的网络通信协议出来,以解决网络通信中可能存在的粘包问题++
而解决粘包有三种方式:
- 特殊字符间隔(如果数据有特殊字符就要另外处理)
- 定长(有些数据大有些数据小,不好控制)
- LV格式。
(2)本项目中将使用LV格式来定义应用层的通信协议格式:
- Length:该字段固定4字节长度,用于表示后续的本条消息数据长度。
- MType:该字段为Value中的固定字段,固定4字节长度,用于表示该条 消息的类型。
-
- Rpc调用请求/响应类型消息。
- 发布/订阅/取消订阅/消息推送类型消息。
- 主题创建/删除类型消息。
- 服务注册/发现/上线/下线类型消息。
- IDLength:为消息中的固定字段,该字段固定4字节长度,用于描述后续ID字段的实际长度。
- MID:在每条消息中都会有⼀个固定字段为ID字段,用于唯⼀标识消息,ID字段⻓度不固定。
- Body:消息主题正⽂数据字段,为请求或响应的实际内容字段。
2.3 Dispatcher模块
(1)
模块存在的意义:
区分消息类型,根据不同的类型,调⽤不同的业务处理函数进行消息处理。
- 当muduo库底层通信收到数据后,在onMessage回调函数中对数据进行应用层协议解析,得到⼀条实际消息载荷后
我们就该决定这条消息代表这客户端的什么请求,以及应该如何处理。
- 因此,我们设计出了Dispatcher模块,作为⼀个分发模块,这个模块内部会保存有⼀个hash_map<消息类型,回调函数>
- 以此由使用者来决定哪条消息 用哪个业务函数进行处理,当收到消息后,在该模块找到其对应的处理回调函数进行调用即可。
(2)消息类型:
- rpc请求&响应。
- 服务 注册/发现/上线/下线请求&响应。
- 主题 创建/删除/订阅/取消订阅请求&响应,消息发布的请求&响应。
2.4 RpcRouter模块
(1)
RpcRouter模块存在的意义:
- 提供rpc请求的 处理回调函数,内部所要实现的功能,分辨出客户端请求的服务进⾏处理得到结果进行响应。
(2)
rpc请求中,最关键的两个点:
- 请求方法名称。
- 请求对应要处理的参数信息。
在Rpc远端调用中,
- 首先将客户端到服务端的通信链路打通,
- 然后将自己所需要调用的服务名称,以及参数信息传递给服务端,
- 由服务端进行接收处理,并返回结果。
不管是 ++客户端要传递给服务端的服务名称以及参数信息,或者 服务端返回的结果++ ,都是在上边Protocol中定义的Body字段中,因此Body字段中就存在了另⼀层的正文序列化/反序列化过程。
(3)
序列化方式有很多种,鉴于当前我们是json-rpc,因此这个序列化过程我们就初步使用json序列化来进行,所定义格式如下:
//RPC-request
{
"method" : "Add",
"parameters" :
{
"num1" : 11,
"num2" : 22
}
}
//RPC-response
{
"rcode" : OK,
"result": 33
}
{
"rcode" : ERROR_INVALID_PARAMETERS
}
需要注意的是
- 在服务端,当接收到这么⼀条消息后,
- Dispatcher模块会找到该 Rpc请求类型的回调处理函数进行业务处理,
- 但是在进行业务处理的时候,也是只会将 parameters 参数字段传⼊回调函数中进行处理。
但是对服务端来说,应该从传⼊的Json::Value对象中,有什么样的参数,以及参数信息是否符合自己所提供的服务的要求,都应该有⼀个检测,是否符合要求,符合要求了再取出指定字段的数据进行处理。
(4)
因此,对服务端来说,在进行服务注册的时候,必须有⼀个服务描述,以代码段中的Add请求为例,该服务描述中就应该描述:
- 服务名称: Add,
- 参数名称: num1,是⼀个整形
- 参数名称: num2,是⼀个整形,
- 返回值类型:整形
有了这个描述,在 回调函数中就可以先对传⼊的参数进行校验,没问题了则取出指定字段数据进行处理并返回结果
(5)
基于以上理解,在实现该模块时,该有以下设计:
- 该模块必须具备⼀个Rpc路由管理,其中包含对于每个服务的参数校验功能。
- 该模块必须具备⼀个方法名称和方法业务 回调的映射。
- 该模块必须向外提供 Rpc请求的 业务处理函数。
2.5 Publish-Subscribe模块
(1)Publish-Subscribe模块存在的意义:
针对发布 主题订阅 请求进行处理,提供⼀个回调函数设置给Dispatcher模块。
(2)发布订阅所包含的请求操作:
- 主题的创建
- 主题的删除
- 主题的订阅
- 主题的取消订阅
- 主题消息的发布
在当前的项目中,我们也实现⼀个简单的发布订阅功能,该功能是围绕 多个客户端与⼀个服务端来展开的。
也就是 任意⼀个客户端在发布或订阅之前先创建⼀个主题,比如在新闻发布中我们创建⼀个音乐新闻主题,哪些客户端希望能够收到音乐新闻相关的消息,则就订阅这个主题,服务端会建立起该主题与客户端之间的联系。
++当某个客户端向服务端发布消息,且发布消息的目标主题是音乐新闻主题,则服务端会找出订阅了该主题的客户端,将消息推送给这些客户端。++
(3)既然涉及到网络通信,那就先将通信消息的正文格式定义出来:
//Topic-request
{
"key" : "music", //主题名称
// 主题操作类型
"optype" :
TOPIC_CRAETE/TOPIC_REMOVE/TOPIC_SUBSCRIBE/TOPIC_CANCEL/TOPIC_PUBLISH,
//TOPIC_PUBLISH请求才会包含有message字段
"message" : "Hello World"
}
//Topic-response
{
"rcode" : OK,
}
{
"rcode" : ERROR_INVALID_PARAMETERS,
}
(4)功能思想并不复杂,所以将精力放到其实现设计上:
- 该模块必须具备⼀个主题管理,且主题中需要保存订阅了该主题的客户端连接:
-
- 主题收到⼀条消息,需要将这条消息推送给订阅了该主题的所有客户端
- 该模块必须具备⼀个订阅者管理,且每个订阅者描述中都必须保存自己所订阅的主题名称:
-
- 目的是为了当⼀个订阅客⼾端断开连接时,能够找到订阅信息的关联关系,进行删除
- 该模块必须向外提供 主题创建/销毁,主题订阅/取消订阅,消息发布处理的业务处理函数。
2.6 Registry-Discovery模块
(1)Registry-Discovery模块存在的意义:就是针对服务注册与发现请求的处理。
(2)服务注册/发现类型请求中的详细划分:
- 服务注册:服务provider告诉中转中心,自己能提供哪些服务。
- 服务发现:服务caller询问中转中心,谁能提供指定服务。
- 服务上线:在⼀个provider上线了指定服务后,通知发现过该服务的客⼾端有个provider可以提供该服务。
- 服务下线:在⼀个provider断开连接,通知发现过该服务的caller,谁下线了哪个服务。
服务注册模块,该模块主要是为了实现分布式架构而存在,让每⼀个rpc客户端能够从不同的节点主机上获取⾃⼰所需的服务,让业务更具扩展性,系统更具健壮性。
- ⽽为了能够让rpc-caller知道有哪些rpc-provider能提供⾃⼰所需服务,那么就需要有⼀个注册中⼼让这些rpc-provider去注册登记⾃⼰的服务,让rpc-caller来发现这些服务。
(3)因此,在我们的服务端功能中,还需实现服务的注册/发现,以及服务的上线/下线功能。
//RD--request
{
//SERVICE_REGISTRY-Rpc-provider进⾏服务注册
//SERVICE_DISCOVERY - Rpc-caller进⾏服务发现
//SERVICE_ONLINE/SERVICE_OFFLINE 在provider下线后对caller进⾏服务上下线通知
"optype" : SERVICE_REGISTRY/SERVICE_DISCOVERY/SERVICE_ONLINE/SERVICE_OFFLINE,
"method" : "Add",
//服务注册/上线/下线有host字段,发现则⽆host字段
"host" :
{
"ip" : "127.0.0.1",
"port" : 9090
}
}
//Registry/Online/Offline-response
{
"rcode" : OK,
}
//error-response
{
"rcode" : ERROR_INVALID_PARAMETERS,
}
//Discovery-response
{
"method" : "Add",
"host" :
[
{"ip" : "127.0.0.1","port" : 9090},
{"ip" : "127.0.0.2","port" : 8080}
]
}
(4)该模块的设计如下:
- 必须具备⼀个服务发现者的管理:
-
- 方法与发现者:当⼀个客⼾端进行服务发现的时候,进行记录谁发现过该服务,当有⼀个新的提供者上线的时候,可以通知该发现者。
- 连接与发现者:当⼀个发现者断开连接了,删除关联关系,往后就不需要通知了。
- 必须具备⼀个服务提供者的管理:
-
- 连接与提供者:当⼀个提供者断开连接的时候,能够通知该提供者提供的服务对应的发现者,该主机的该服务下线了。
- 方法与提供者:能够知道谁的哪些方法下线了,然后通知发现过该方法的客户端。
- 必须向Dispatcher模块提供⼀个服务注册/发现的业务处理回调函数。
这样,当⼀个rpc-provider登记了服务,则将其管理起来,当rpc-caller进行服务发现时,则将保存的对应服务所对应的主机信息,响应给rpc-caller。
- 当中途⼀个rpc-provider上线登记服务时,则可以给进行了对应服务发现的rpc-caller进行服务上线通知,通知rpc-caller当前多了⼀个对应服务的rpc-provider。
(5)同时,当⼀个rpc-provider下线时,则可以找到进行了该服务发现的rpc-caller进行服务的下线通知。
2.7 Server
(1)当以上的所有功能模块都完成后,我们就可以将所有功能整合到⼀起来实现服务端程序了。
- RpcServer:rpc功能模块与网络通信部分结合。
- TopicServer:主题 发布订阅功能模块与网络通信部分结合。
- RegistryServer:服务发现注册功能模块与⽹络通信部分结合
(2)RpcServer结构图:
(3)RegistryServer结构图:
(4)TopicServer结构图: