分布式RPC框架实战(二):服务端模块划分与底层设计

前言

在上一篇中,我们理解了分布式 RPC 的核心思想。在服务端,它不仅要处理远程调用(RPC),还要兼顾服务注册发现(Registry)和消息发布订阅(Pub/Sub)。为了实现高内聚、低耦合,我们将服务端拆分为 7 个核心模块。今天我们就来逐一拆解这些模块的设计思路

6.2.1 服务端模块划分总览

首先,我们需要明确服务端的功能需求。本质上,服务端需要基于网络通信接收客户端的请求,并提供以下三类服务:

提供PRC服务

提供服务注册与发现、上线&下线通知

提供主题操作(创建/删除/订阅/取消)及消息发布

基于以上功能需求,我们在服务端划分出了7个模块,为了方便理解,用文字结构来表示它们的层级关系:

复制代码
[7. Server (顶层服务组装)]
  ├── [4. RpcRouter (RPC业务路由)]
  ├── [5. Publish-Subscribe (发布订阅业务)]
  ├── [6. Registry-Discovery (服务注册发现)]
  └── 以上三者,均依赖 ↓
[3. Dispatcher (消息分发器)]
  └── 依赖 ↓
[2. Protocol (应用层通信协议)]
  └── 依赖 ↓
[1. Network (底层网络通信 Muduo)]

接下来,我们自底向上,逐一刨析每个模块的核心职责

6.2.1.1 Network(网络通信模块)

  • 模块存在的意义:这是整个服务端的基石。该模块为网络通信模块,实现底层的网络通信功能。

  • 技术选型 :因为网络通信模块本质上是一个比较复杂庞大的模块。因此,鉴于项目的庞大,我们将使用陈硕大佬的 Muduo 库来进行搭建。

  • 对外接口 :在 Muduo 库中,为了让服务端/客户端对消息进行处理,我们需要设置一个 onMessage 回调函数。在这个函数中,我们将对收到的数据进行应用层协议处理(这就引出了下一个模块)

6.2.1.2 Protocol(应用层通信协议模块)

模块存在的意义:解析数据,解决通信中可能存在的粘包问题,能够获取到一条完整的消息

解决粘包的方法:通常有三种方式(特殊字符间隔、定长、LV格式)。本项目中将使用LV格式来定义应用层的通信协议格式

如图所示,我们的通信协议头包含以下字段:

1、Length(4)字节:该字段固定4字节长度,用于表示后续本条消息数据长度

2、MType(4)字节:该字段为Value中的固定字段,固定4字节长度,用于表示该条消息的类型,主要包括:

  • Rpc调用请求/响应类型消息
  • 发布/订阅/取消订阅/消息推送类型消息
  • 主题创建/删除类型消息
  • 服务注册/发现/上限/下线类型消息

3、LDLength(4字节):为消息中的固定字段,固定4字节长度,用于描述后续ID字段的实际长度

4、MID(变长):在每条消息中都会有一个固定字段为ID字段,用于唯一标识消息,ID字段长度不固定

5、Body(变长):消息主题正文数据字段,为请求或响应的实际内容字段

6.2.1.3 Dispatcher(消息分发处理模块)

  • 模块存在的意义:区分消息类型,根据不同的类型,调用不同的业务处理函数进行消息处理。

  • 工作原理 :当 Muduo 库底层通信收到数据后,在 onMessage 回调函数中对数据进行应用层协议解析,得到一条实际消息载荷后,我们就该决定这条消息代表这客户端的什么请求,以及应该如何处理。

  • 核心设计 :我们设计出了 Dispatcher 模块,作为一个分发模块。这个模块内部会保存有一个 hash_map<消息类型, 回调函数>。当收到消息后,在该模块找到其对应的处理回调函数进行调用即可

  • 消息类型(MType)分类:

    1. RPC 请求&响应

    2. 服务注册/发现/上线/下线请求&响应

    3. 主题创建/删除/订阅/取消订阅请求&响应,消息发布的请求&响应

6.2.1.4 RpcRouter(远端调用路由功能模块)

  • 模块存在的意义:提供 RPC 请求的处理回调函数,内部所要实现的功能,分辨出客户端请求的服务进行处理得到结果进行响应。

  • 核心关注点 :RPC 请求中,最关键的两个点:请求方法名称 和 请求对应要处理的参数信息。

  • 数据序列化(JSON-RPC):在 RPC 远程调用中,首先将客户端到服务端的通信链路打通,然后将自己所需要调用的服务名称,以及参数信息传递给服务端,由服务端进行接收处理,并返回结果。本项目序列化过程我们就初步使用 JSON 序列化来进行

  • 数据格式示例:

    bash 复制代码
    // RPC-request
    {
      "method": "Add",
      "parameters": { "num1": 11, "num2": 22 }
    }
    // RPC-response
    { "rcode": "OK", "result": 33 }
  • 服务端参数校验设计 :

    服务端收到消息后,只会将 parameters 参数字段传入回调函数中进行处理。但服务端应该对传入的 JSON 对象进行检测,看是否符合自己所提供的服务的要求。

    因此,在服务端进行服务注册的时候,必须有一个服务描述(ServiceDescriptor) ,以 Add 为例:

    • 服务名称:Add

    • 参数名称:num1,是一个整型

    • 参数名称:num2,是一个整型

    • 返回值类型:整型

  • 模块设计要点:

    1. 必须具有一个 Rpc 路由管理,其中包含对于每个服务的参数校验功能。

    2. 必须具备一个方法名称和方法业务回调的映射。

    3. 必须向外提供 Rpc 请求的业务处理函数。

6.2.1.5 Publish-Subscribe(发布订阅功能模块)

  • 模块存在的意义:针对发布订阅请求进行处理,提供一个回调函数设置给 Dispatcher 模块。

  • 包含的请求操作:主题的创建、删除、订阅、取消订阅、消息的发布。

  • 简单场景理解:即,任意一个客户端在发布或订阅之前先创建一个主题。比如在新闻发布中我们创建一个音乐新闻主题,哪些客户端想要收到音乐新闻相关的消息,就订阅这个主题。当某个客户端向服务端发布消息,且发布消息的目标主题是音乐新闻主题,则服务端会找出订阅了该主题的客户端,将消息推送给这些客户端。

  • 通信消息格式定义:

    bash 复制代码
    // Topic-request
    {
      "key": "music", // 主题名称
      "optype": "TOPIC_PUBLISH", // 操作类型
      "message": "Hello World" // 消息内容
    }
    // Topic-response
    { "rcode": "OK" }
  • 模块设计要点(重点):

    1. 该模块必须具备一个主题管理,且主题中需要保存订阅了该主题的客户端连接。

      • 目的:当收到消息,需要将这条消息推送给订阅了该主题的所有客户端。
    2. 该模块必须具备一个订阅者管理,且每个订阅者描述中都必须保存自己所订阅的主题名称。

      • 目的:为了当一个订阅客户端断开连接时,能够找到订阅信息的关联关系,进行删除。
    3. 该模块必须向外提供主题创建/删除,主题订阅/取消订阅,消息发布处理的业务处理函数

6.2.1.6 Registry-Discovery(服务注册/发现/上线/下线功能模块)

  • 模块存在的意义:针对服务注册与发现请求的处理。

  • 包含的请求操作:

    • 服务注册:服务 provider 去注册中心中,自己能提供哪些服务。

    • 服务发现:服务 caller 去注册中心中,能查询到提供某个服务的 provider 地址。

    • 服务上线:在一个 provider 上线了指定服务后,通知发现过该服务的客户端有个 provider 可以提供该服务。

    • 服务下线:在一个 provider 断开连接,通知发现过该服务的客户端,谁下线了哪个服务。

  • 为什么需要注册中心?:为了能够让 rpc-caller 知道有哪些 rpc-provider 能够提供自己所需要服务,那么就需要一个注册中心让这些 rpc-provider 去注册登记自己的服务,让 rpc-caller 来发现这些服务

  • 通信消息格式定义:

    bash 复制代码
    // 服务注册
    { "method": "Add", "host": "127.0.0.1", "port": 9000 }
    // 服务发现响应
    { "method": "Add", "host": [{"ip": "127.0.0.1", "port": 9000}] }
  • 模块设计要点(重点):

    1. 必须具备一个服务发现者的管理:

      • 方法A(服务发现):当一个客户端进行服务发现的时候,进行记录谁发现过该服务。当有一个新的提供者上线的时候,可以通知发现者。

      • 方法B(连接与发现者):当一个发现者断开连接了,删除关联关系,往后就不需要通知了。

    2. 必须具备一个服务提供者的管理:

      • 方法A(连接与提供者):当一个提供者断开连接的时候,能够通知提供者提供的服务对应的发现者,让该主机下线了。

      • 方法B(连接与提供者):能够知道谁提供的方法下线了,然后通知发现过该方法的客户端。

    3. 必须为 Dispatcher 模块提供一个服务注册/发现处理的业务处理回调函数

6.2.1.7 Server(顶层服务组装模块)

  • 模块存在的意义:当以上的所有功能模块都完成后,我们就可以将所有功能整合到一起来实现服务端程序了。

  • 组装逻辑:

    • RpcServer:RPC 功能模块与网络通信部分结合。

    • RegistryServer:服务发现注册功能模块与网络通信部分结合。

    • TopicServer:发布订阅功能模块与网络通信部分结合

相关推荐
web打印社区1 小时前
C-Lodop 提示未准备好或 WebSocket 没准备好:先让本机服务起来
javascript·网络·websocket·网络协议·pdf·html
勇往直前plus2 小时前
HTTPS 详解
网络协议·http·https
别动我齐刘海2 小时前
简历技术栈全面复习总结
c++·人工智能·深度学习·学习·tcp/ip·算法·rpc
wdfk_prog3 小时前
Wi-Fi Direct 源码分析(11):P2P-GROUP-STARTED 之后——Group Interface、IP 配置与真实数据通路
运维·服务器·网络协议·tcp/ip·asp.net·p2p·wifi-direct
wechatbot8883 小时前
企业微信HTTP协议接口完整接入教程|全量消息收发 API 实战
网络协议·http·微信·企业微信·ai编程
网络小江3 小时前
上网第三十七课:HTTPS——地址栏那把小锁,到底保护了什么
网络协议
车载中间件通信3 小时前
[TC8测试]TCP_CALL_ABORT_03_03
网络·网络协议·tcp/ip·tc8
pt10434 小时前
CML网络仿真入门-1:Cisco Modeling Labs概述
运维·网络协议
网络小江4 小时前
上网第三十六课:HTTP——浏览器背后的协议
网络协议