目录
目录README.md

RPC Framework 项目报告

Overview

🔗gitlink仓库

🔗Get Started

🔗本文档更新地址

🔗项目wiki

Features

✅ 基于Socket接口编程

✅ 平台化,RegisterFactory快速注册服务

✅ 跨语言调用:支持两种跨语言调用方法:Python-C++ & Python-Java

✅ IDL & IDL Compiler

✅ 基于线程池的并发模型

演示

python-c++远程调用

上图演示了两个终端之间的远程调用:左侧终端运行了提供C++服务的Server,右侧终端Client通过python程序调用Server的C++服务。

上图演示了Client通过远程调用,监测Server的性能指标,包括CPU利用率,内存利用率和磁盘利用率。

项目概述

本项目实现了一个基础功能完整的RPC(远程过程调用)框架,该框架完全基于Python Socket编程,包含两个子框架,分别支持Python-Java、Python-C++跨语言调用、IDL定义和高并发处理。此外,我们的框架具有平台化特性,能够动态注册和管理多种服务,并提供了详细的注释和示例,以方便后续完善和开发新的功能。

设计目标与原则

设计目标

  1. 教育性: 从零实现RPC,便于深入理解分布式系统基本原理
  2. 平台化: 支持动态服务注册,不局限于特定方法调用
  3. 跨语言: 基于标准协议,支持跨语言调用,支持Python、C++、Java客户端以及Python、C++服务端
  4. 高性能: 多线程并发处理,支持大量的并发连接
  5. 易扩展: 模块化设计,便于功能扩展和定制

设计原则

  • 简单性: 框架设计直观,没有较多的函数依赖,易上手和学习基本原理
  • 模块化: 采用模块化设计,各组件功能清晰,松耦合
  • 标准化: 使用通用的消息格式,便于跨语言实现
  • 可靠性: 具有完善的错误报错和异常处理机制,能够通过详细和完善的评估
  • 可观测: 内建监控和统计功能
  • 文档化: 详细的代码注释和使用文档

系统架构设计

整体架构图

┌─────────────────────────────────────────────────────────────────┐
│                           RPC Framework                          │
├─────────────────────────────────────────────────────────────────┤
│                        Client Layer                             │
│      ┌─────────────┐  ┌─────────────┐  ┌─────────────┐                |
│      │   Python    │  │     Java    │  |        C++     |             |
│      │   Client    │  │    Client   │  |       Client    |             |
│      └─────────────┘  └─────────────┘  └─────────────┘             |
├─────────────────────────────────────────────────────────────────┤
│                             Protocol Layer                         │
│  ┌─────────────────────────────────────────────────────────────┐│
│  │     RPC Protocol (Binary Header + JSON Body)                ││
│  │  Magic(4) | Type(1) | Length(4) | JSON Message Body         ││
│  └─────────────────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────────────────┤
│                       Server Layer                              │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐              │
│  │   Socket    │  │  Service    │  │   Thread    │              │
│  │   Server    │  │  Registry   │  │    Pool     │              │
│  └─────────────┘  └─────────────┘  └─────────────┘              │
├─────────────────────────────────────────────────────────────────┤
│                        Tool Layer                               │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐              │
│  │     IDL     │  │    Code     │  │ Serializer  │              │
│  │  Compiler   │  │ Generator   │  │   Manager   │              │
│  └─────────────┘  └─────────────┘  └─────────────┘              │
└─────────────────────────────────────────────────────────────────┘

项目结构

toyRPC/
├── requirements.txt
├── src/
│   ├── RPC-CPP/
│   │   ├── cc/
│   │   │   ├── Calculator.hpp
│   │   │   ├── Calculator.cc
│   │   │   ├── functions.hpp
│   │   │   ├── functions.cc
│   │   │   ├── functions.pxd
│   │   │   ├── functions.pyx
│   │   │   ├── py_functions.py
│   │   │   ├── setup.py
│   │   │   ├── main.py
│   │   │   └── README.md
│   │   ├── idl/
│   │   │   ├── cpp_generator.py
│   │   │   └── python_generator.py
│   │   └── python/
│   │       ├── client.py
│   │       ├── server.py
│   │       ├── register.py
│   │       ├── registerFactory.py
│   │       └── Calculator.py
│   ├── RPC-java/
│   │   └── rpc_framework/
│   │       ├── __init__.py
│   │       ├── client.py
│   │       ├── cross_language_adapter.py
│   │       ├── idl_compiler.py
│   │       ├── IDL编译器改进总结.md
│   │       ├── serializer.py
│   │       ├── registry.py
│   │       ├── protocol.py
│   │       └── server.py
│   └── tests/
│       ├── example-provider-consumer/
│       │   ├── consumer.py
│       │   └── provider.py
│       ├── example-remote-monitor/
│       │   ├── monitor.py
│       │   └── server.py
│       ├── examples/
│       │   ├── example_client.py
│       │   ├── example_idl.py
│       │   ├── example_service.py
│       │   ├── service.idl
│       │   ├── test_idl_generate_java_client.py
│       │   ├── test_incomplete_rpc_framework.py
│       │   ├── test_simple_rpc_framework.py
│       │   └── idl_test/
│       │       ├── python/
│       │       │   ├── test_cal.py
│       │       │   ├── test_eq.py
│       │       │   └── test_ut.py
│       │       └── java/
│       │           ├── test_cal.java
│       │           ├── test_eq.java
│       │           └── test_ut.java
│       └── example-idl/
│           ├── example_cc/
│           │   ├── idl_client.cpp
│           │   └── idl_server.cpp
│           └── example_python/
│               ├── client.py
│               └── server.py

该项目分为Python-Java框架、Python-C++框架两个部分,以实现Python和Java、Python和C++两种不同的跨语言互操作性。其中src目录下,RPC-CPP和RPC-Java目录分别是两个不同子框架的核心RPC实现。tests目录下examples目录是Python-Java子框架的示例代码,其余example目录是Python-C++子框架的示例代码。

组件实现

网络通信协议

Python-Java子框架

该子框架在网络通信协议组件中,主要定义了RPC通信的消息类型、消息格式和协议处理功能。其中,消息类型由请求(REQUEST)、响应(RESPONSE)、错误(ERROR)和心跳(HEARTBEAT)4部分组成,主要作用是为协议通信提供类型约束。消息格式常量的定义如下表所示,由头部和消息体组成。头部开始是魔法数字(MAGIC:),用于协议识别和验证;然后是消息类型,用于识别消息的类型;最后是消息体的长度,便于消息体的正确处理。在该定义中,由于消息头部的长度是固定的,固定9字节头部(4+1+4),从而可以确保后续解析的可靠性;消息体采用JSON+二进制格式,提高了消息传输的效率以及可读性和扩展性。

+----------+----------+----------+----------+
| Magic(4) | Type(1)  | Length(4)| Body(N)  |
+----------+----------+----------+----------+

在我们的具体实现过程中,主要包括了以下的核心方法:

  • pack_message(): 将消息,即消息类型和消息体,打包成二进制格式
  • unpack_message(): 将二进制数据,解包成消息类型和消息体

Python-CPP子框架

该子框架客户端和服务端之间的消息格式直接基于JSON实现,没有复杂的头部信息,实现相对简单。消息发送方使用JSON进行消息的序列化和反序列化,将信息封装为JSON对象,然后调用Json::FastWriter将其转换为字符串,通过套接字进行传输。消息接收方接受到字符串数据后,再使用Json::Reader将字符串解析为 JSON 对象进行处理。 其主要包括的核心方法为:

  • 打包消息
    • json.dumps((__name, args, kwargs)).encode():封装JSON对象
    • Json::FastWriter:将JSON对象转换为字符串
  • 解包消息
    • json.loads(client.recv(self.SIZE).decode()):解码JSON对象
    • Json::Reader:将字符串解析为 JSON 对象

消息序列化模块

Python-Java子框架

在设计时,我们采用了策略模式和工厂模式,设计的消息序列化模块的核心功能是:

  • 提供消息的序列化和反序列化功能
  • 支持跨语言的数据交换

同时,我们在设计时考虑到了扩展性和灵活性,提供了序列化器基类,使得后续工作者可以自行配置选择合适的序列化方式,也支持多种序列化方式的无缝切换,方便在不同场景下使用。在我们的具体实现过程中,主要实现了以下的核心方法:

  • SerializationType:枚举定义层,定义支持的序列化类型(JSON、Pickle),便于扩展
  • BaseSerializer:序列化器基类,定义了序列化器接口
  • JSONSerializer: 跨语言兼容的JSON序列化,包含复杂对象处理逻辑
  • MessageSerializer: 消息序列化管理器,综合上述核心方法的类,用于管理消息的序列化和反序列化,同时屏蔽了底层实现细节,体现了开闭原则和单一职责原则

Python-CPP子框架

RPC-CPP 子框架的消息序列化模块通过 JSON 实现消息的序列化与反序列化,支持跨语言数据交换,并具备良好的扩展性与灵活性。主要实现了以下核心功能:

  • 消息处理:通过 JSON 格式实现消息的序列化与反序列化,确保客户端与服务端间的数据传递。
  • 跨语言支持:采用通用的 JSON 格式,支持不同语言编写的客户端与服务端间的数据交换

核心方法介绍如下:

  • 序列化方法
    • 请求处理:将请求信息(服务名、方法名、参数等)转为 JSON 字符串,便于网络传输。
    • 响应处理:将处理结果转为 JSON 字符串,返回客户端。
  • 反序列化方法
    • 请求解析:将接收到的 JSON 请求字符串解析为可处理的数据结构。
    • 响应解析:将接收到的 JSON 响应字符串解析为客户端可理解的数据结构。

服务注册表

Python—Java子框架

Python—Java子框架服务注册表实现了平台化的服务注册、发现和管理功能,具体功能包括但不限于:

  • 提供了服务发现功能
  • 提供了动态服务注册,同时支持批量注册
  • 服务状态管理功能:支持服务状态切换和监控
  • 服务调用统计功能: 自动记录调用次数、注册时间等运营数据

我们在设计的过程中,采用了典型的“注册-发现-调用”模式,用以提供RPC框架的核心服务管理能力。在我们的具体实现过程中,主要实现了以下的核心方法:

  • ServiceStatus:服务状态枚举,通过枚举的方式来定义服务的三种状态(可用/不可用/废弃)
  • ServiceInfo:服务信息类,该类用以封装服务的完整信息,包括元数据、状态和统计信息
  • ServiceRegistry:服务注册表的实现,提供了平台化的服务管理能力,便于服务的全生命周期管理

这种设计也能体现课堂上,丁老师所传授的高内聚、低耦合的思想,从而为分布式服务提供了可靠的本地注册管理能力。在具体进行实验模拟时,注册表会经历注册、发现和调用三个阶段。在注册阶段,首先会创建一个服务存储字典,其中键为服务名称,值为ServiceInfo对象,用以后续的服务添加和调用;在发现阶段,通过服务名在字典中查找对应的ServiceInfo对象,并返回该对象;在调用阶段,首先会检查服务的状态,如果服务状态可用,则进行调用,否则返回错误信息。

Python-CPP子框架

Python-CPP 子框架的服务注册表模块实现了服务的注册、管理与调用功能,通过装饰器的方式简化服务注册流程,为客户端提供了便捷的服务发现与调用途径,确保了服务的集中管理和高效调用。其核心组件包含以下几类:

  • 装饰器
    • registerMethod:用于注册单个方法,将函数添加到服务端的方法列表中。
    • registerInstance:用于注册类的实例,将类实例的非特殊方法添加到服务端的方法列表中。
  • 服务类
    • Calculator:包含基础的数学运算方法,如加法、减法、乘法和除法。
    • systemMonitor:提供系统监控功能,如获取 CPU 使用率、内存使用率和磁盘使用率。
  • 辅助方法
    • listAllMethods:返回服务端所有已注册方法的名称列表。
    • invoke:允许客户端通过方法名动态调用服务。

这种设计同样体现了高内聚、低耦合的思想,服务的注册、发现和调用功能相互独立,便于维护和扩展。服务的注册逻辑封装在Server类中,服务的发现和调用逻辑由客户端实现,降低了模块之间的耦合度。同时,每个模块内部的功能紧密相关,提高了内聚性。

服务端

Python-Java子框架

在Python-Java子框架RPC服务端的设计过程中,我们主要采用了分层架构,将网络通信、协议处理、服务管理等功能进行了分离。在实现过程中,我们并未使用已有的开源架构,而是基于Python语言Socket库的TCP通信,主要实现了以下的核心方法:

  • ClientConnection: 客户端连接管理,类中封装了客户端的信息,如套接字、地址、创建时间等信息
  • RPCServer: 服务端,类中封装了大量的函数用来协调各个组件工作:
    • _register_builtin_services: 注册内建系统服务,如服务端状态、ping测试、服务列表以及检测客户端语言等
    • register_service: 用以注册服务
    • register_class_services:该函数是register_service的升级,支持批量注册类的方法为服务
    • start/stop:启动/停止服务端
    • _handle_client:处理客户端连接,主要用来接受数据、提取完整的消息、处理消息和清理连接等功能
    • _handle_request:处理RPC请求,对消息体解包后,检测客户端的语言,然后将客户端语言转换为服务端默认的语言,最后对转换的结果打包。
    • _process_message:处理RPC消息,对消息进行解包,识别消息类型,然后处理对应的RPC请求

此外,我们在类中还采用了并发处理策略。当主线程接受到新的连接时,服务端会创建一个ClientConnection对象,并将其添加到连接池中;然后,线程池会处理客户端请求,避免阻塞;最后,使用锁机制保护共享资源。

Python-CPP子框架

在Python-CPP 子框架服务端的设计中,同样采用了分层架构,将网络通信、服务注册与管理等功能进行了分离。该服务端基于 Python 语言的socket库实现 TCP 通信,主要实现了以下核心方法:

  • Server:这是Python-CPP子框架服务端的核心类,封装了服务端的核心功能,用于协调各个组件的工作
    • registerMethod(function): 用于注册单个函数作为服务。该方法将传入的函数添加到methods字典中,键为函数名,值为函数对象。如果传入的不是函数对象,会抛出异常。
    • registerInstance(instance): 用于注册类的实例方法作为服务。该方法遍历实例的所有非特殊方法(即方法名不以__开头),并将这些方法添加到methods字典中。如果传入的不是类的实例对象,会抛出异常。
    • __handle__(client, address): 处理单个客户端的请求。该方法在一个循环中不断接收客户端发送的请求,将请求解析为函数名、位置参数和关键字参数。然后尝试调用methods字典中对应的函数,并将结果发送回客户端。如果调用过程中出现异常,将异常信息发送回客户端。当客户端断开连接时,关闭客户端套接字。
    • run(): 启动服务端。该方法创建一个 TCP 套接字,绑定到指定的地址和端口,并开始监听客户端连接。使用Thread类为每个新的客户端连接创建一个新的线程,调用__handle__方法处理该客户端的请求,避免阻塞主线程。可以通过KeyboardInterrupt异常来停止服务端。

Python-CPP子框架的服务端也采用了并发处理策略来提高性能。当主线程接收到新的客户端连接时,会为该连接创建一个新的线程,调用__handle__方法处理该客户端的请求。这样可以避免单个客户端的请求阻塞其他客户端的处理。同时,使用Lock机制保护_instance变量,确保Server类的单例模式在多线程环境下的线程安全。

客户端

Python-Java子框架

在RPC客户端中,我们设计了远程方法调用接口。在设计过程中,我们通过UUID生成唯一请求ID,建立请求与响应的对应关系,并使用RLock保护共享资源,支持多线程并发调用,此外,我们还设计了异步通信模式,使用独立的接收线程处理服务器响应,主线程通过事件机制等待结果。在实现过程中,我们主要实现了以下的核心方法:

  • connect/disconnect: 连接/断开RPC服务器
  • _cleanup:当所有等待的请求执行完后,将其他请求资源全部清空
  • call:调用远程方法,创建请求 → 发送数据 → 等待事件 → 获取结果
  • _receive_messages:接收消息的后台线程,解析消息 → 分发处理 → 触发事件
  • _handle_response:主要用来处理正常响应,将消息类型为RESPONSE的消息添加到请求结果列表中

由于我们的设计结构实现了发送和接收分离,在实际运行过程中,大大提高了并发性能,减少了冗余程序带来的性能降低影响。

Python-CPP子框架

在Python-CPP子框架客户端的设计中,我们实现了远程方法调用接口,通过 UUID 生成唯一请求 ID 建立请求与响应的对应关系,并使用线程安全机制支持多线程并发调用。客户端采用发送和接收分离的设计模式,提升了并发性能。以下是核心方法的介绍:

  • Client:这是Python-CPP子框架的核心类,封装了客户端的核心功能
    • connect(host, port): 连接服务器。创建 TCP 套接字并连接到指定地址和端口,启动接收线程处理服务器响应。
    • disconnect(): 断开与服务器的连接。停止接收线程,关闭套接字。
    • call(function_name, *args, **kwargs): 调用远程方法。生成唯一请求 ID,创建请求对象,发送请求数据并等待响应结果。
    • _receive_messages(): 接收消息的后台线程。持续从服务器接收数据,解析消息并分发处理。
    • _handle_response(response): 处理服务器响应。根据请求 ID 找到对应的事件对象,设置响应结果并触发事件。

Python-CPP子框架客户端通过高效的设计和实现,提供了简洁而强大的远程方法调用能力,同时确保了在多线程环境下的性能和稳定性。

跨语言适配器

Python-Java子框架

跨语言适配器主要用来处理不同编程语言间的数据类型转换和协议适配,主要通过统一的抽象层来屏蔽不同编程语言间的差异。在设计时,我们采用了课上老师所教导的思路,使用枚举法和映射表来进行实现,在后续增加序列化格式时,我们发现了这种设计方法具有较高的可扩展性,很方便后续添加新的语言支持。我们的跨语言适配器具有:自动数据类型转换、Java和Python两种序列化格式支持以及语言特定的协议适配三种功能。在实现过程中,我们主要实现了以下的核心方法:

  • LanguageType: 用以定义所支持的编程语言(Python、Java)
  • DataType: 定义跨语言通用数据类型(null、boolean、integer等)
  • CrossLanguageAdapter:跨语言适配器的核心类,包含4项核心功能:
    • _convert_*:类型映射函数,以映射表的形式,来维护不同编程语言的数据类型
    • _infer_data_type:类型推断函数,用来推断数据的类型,便于后续跨语言值的转换
    • detect_language:语言检测函数,可以从请求头自动识别客户端语言
    • _python_serialize/_python_deserialize:Python序列化器,为Python语言提供专用的序列化/反序列化方法
    • _java_serialize/_java_deserialize:Java序列化器,为Java语言提供专用的序列化/反序列化方法

在跨语言调用测试中,因为不同语言的协议具有差异,如Java的jsonrpc字段,所以我们还创建了协议处理器,针对性地为不同语言创建特定格式的请求/响应,从而屏蔽了繁杂的处理细节。

Python-CPP子框架

Python-CPP 子框架的跨语言适配器通过统一的接口设计和类型映射机制,实现了不同编程语言间的数据类型转换和协议适配。虽然代码结构较 Python-Java 子框架更为精简,但核心功能依然完整:

  • 类型系统与映射机制:Python-CPP子框架通过隐式类型映射支持跨语言通信,主要依赖 JSON 作为中间表示格式,结合 Python 的动态类型特性简化转换逻辑。
  • 序列化与反序列化:Python-CPP 子框架使用标准 JSON 库进行数据交换,通过预处理和后处理实现语言特定的类型转换。
  • 协议适配层:Python-CPP 子框架通过请求头和响应格式的约定,实现对不同语言客户端的兼容处理。

IDL编译器

Python-Java子框架

IDL编译器主要用于将IDL定义自动地转换为RPC客户端代码,该过程应该是一步到位的,即客户端可直接运行生成的代码,用来调用服务端相应注册的服务。我们在设计的过程中,认识到IDL编译器本质是一个接口描述语言,因此选择了模块化的方式,将解析与生成过程进行分离,这样也易于扩展新语言。对于解析过程,我们使用正则表达式来对IDL语法进行解析;对于生成过程,我们采用“固定样板+具体方法”的生成模式。当解析过后,对于导入函数库、连接方法等固定功能,使用固定的模板进行内容的生成;对于具体的功能,则使用特定的函数进行生成。在实现过程中,我们主要实现了以下的核心方法:

  • DataType:定义了数据类型枚举

  • Parameter: 函数参数的定义

  • Method: 方法的定义

  • Service: 服务的定义

  • IDLCompiler:IDL编译器的核心类,包括解析层和代码生成层:

    • _remove_comments:移除IDL文件的注释内容
    • parse_idl_content:解析IDL文件的内容
    • _parse_services:解析服务定义,如本项目中的基础数学计算
    • _parse_service_body:解析服务体,即解析服务的方法名称、参数列表,返回类型等
    • _parse_method(): 解析方法定义
    • _generate_python/java_client:生成Python/Java客户端代码
    • _generate_python/java_method:生成Python/Java方法代码
    • save_generated_code:保存生成的代码

在测试中,对于Python客户端,由于其语言和服务端相同,很容易便通过了相应的测试;对于Java客户端,我们在测试过程中遇到了较多问题,最大的问题便是消息的格式问题,原因是刚开始对于Java代码的生成,IDL编译器仅能支持Json格式,而服务端是基于二进制格式,为此我们对IDL编译器中的_generate_java_client方法进行了修改,使其能够支持二进制格式。IDL编译器历经三次版本的迭代,具体迭代的内容请参考 IDL编译器改进总结

Python-CPP子框架

IDL 编译器在 Python-CPP 子框架中承担着将 IDL 定义自动转换为 RPC 客户端代码的重要任务,其目标是让客户端能够直接运行生成的代码来调用服务端注册的服务。为了实现良好的扩展性,我们采用模块化设计,将解析过程和代码生成过程分离。解析过程借助正则表达式对 IDL 语法进行分析,生成过程则采用 “固定样板 + 具体方法” 的模式,对于导入函数库、建立连接等固定功能,使用预设模板生成代码,对于具体功能则通过特定函数生成。

  • parse_idl(file_path):位于 RPC-CPP/idl/parser.py 文件中,此函数使用正则表达式解析 IDL 文件内容。它通过匹配 service 关键字来确定服务名称,再通过匹配方法定义来提取方法的返回类型、名称和参数
  • generate_python_code(parsed_result):在 RPC-CPP/idl/python_generator.py 文件里,该函数依据解析结果生成 Python 客户端代码。它使用固定模板创建类和方法的基本结构,再根据具体方法信息填充参数和返回逻辑。
  • generate_cpp_code(parsed_result):RPC-CPP/idl/cpp_generator.py 文件中的此函数根据解析结果生成 C++ 客户端代码。同样采用固定模板构建类和方法结构,依据具体方法信息填充参数和返回逻辑。
  • compile_idl(file_path):位于 RPC-CPP/idl/idl_compiler.py 文件,该函数是 IDL 编译器的核心调用函数。它先调用 parse_idl 解析 IDL 文件,然后分别调用 generate_python_code 和 generate_cpp_code 生成 Python 和 C++ 代码,最后将生成的代码保存到相应文件中。

并发模型

两个RPC子框架下的并发模型均充分利用了线程池和线程安全设计,支持服务端和客户端的多线程并发处理需求,具备良好的性能、可维护性和基础稳定性。模型简洁,专注于线程池管理、连接清理、请求映射和超时控制等核心能力。下面将从服务器并发和客户端并发两个方面进行介绍。

服务器并发

  • ThreadPoolExecutor(线程池处理模型)
    • 服务端统一通过线程池管理工作线程,线程池参数可配置,以便根据部署环境灵活调整并发处理能力。线程池减少了线程频繁创建销毁的系统开销,提升资源利用效率和整体吞吐率。
  • 多线程处理
    • 每个客户端连接由线程池分配的线程负责处理请求和响应。工作线程负责完成请求解码、调用服务处理逻辑、编码响应等操作,确保多个客户端请求可以同时被服务端并发处理,互补阻塞。
  • 连接管理
    • 服务端在接收连接后,对每个连接设置超时时间。当连接长时间无数据交互(读/写)时,会自动关闭该连接,释放资源。
    • 对于异常断开的连接(如客户端非正常退出),线程池中的工作线程会在检测到异常后清理对应的资源。
  • 线程安全
    • 服务注册表、请求分发逻辑等服务端关键数据结构支持并发访问,内部使用线程安全机制(如互斥锁、并发集合等)保证数据一致性。
    • 请求执行过程中的状态信息(如请求上下文、响应数据缓冲区)与工作线程绑定,不共享,不存在跨线程数据竞争。

      客户端并发

  • 异步接收
    • 客户端在发起请求后,将等待响应的逻辑交给独立线程执行,主线程可以继续其他操作或发起更多请求。这种设计减少了主线程阻塞等待的情况,提高了客户端的响应能力和并发请求能力。
  • 请求-响应映射
    • 客户端为每个发起的请求分配全局唯一请求ID,请求ID会随消息一起发送到服务器。服务器响应时会携带相同请求ID,客户端接收到响应后,通过请求ID精确匹配到发起请求的上下文,实现高并发环境下响应的正确分发。
  • 超时控制
    • 每个请求可以配置独立的超时时间。当超时发生时,请求会被主动标记为失败并触发相应的异常或回调。
    • 客户端在超时后会立即释放相关资源,防止资源泄露。
  • 线程安全
    • 客户端请求接口设计为线程安全,支持多个线程同时发起请求。
    • 内部状态均为线程安全结构,避免因并发操作导致数据不一致或程序异常。

操作说明

Python-Java子框架快速开始

本节介绍如何启动Python-Java子框架服务端与客户端,并进行基本功能与高级功能测试。

1.启动服务器

运行以下命令启动服务端:

python ./examples/example_service.py

运行命令后,服务端将在 localhost:8888 启动,并注册以下服务:

  • 基本数学运算服务(加法、减法、除法等)
  • 高级数学运算服务(阶乘、幂运算等)
  • 工具类服务(字符串工具等)

2.启动客户端

2.1 简单的RPC客户端测试

python ./examples/test_simple_rpc_framework.py

该测试将创建并注册一系列客户端服务实例,包括

  • 加运算
  • 乘运算

2.2 完整的RPC客户端测试

python ./examples/test_incomplete_rpc_framework.py

此命令将执行多个功能测试,包括:

  • 序列化器功能测试
  • 单终端客户端-服务端通信测试
  • 多线程并发调用测试
  • 内建服务功能验证

2.3 全面的RPC客户端测试

python ./examples/example_client.py

执行该命令后,客户端将创建并测试与服务端对应的完整服务集:

  • 基本数学运算
  • 高级数学运算
  • 工具服务类

Python-CPP子框架快速开始

本节介绍如何启动Python-CPP子框架服务端与客户端,并进行基本功能与高级功能测试。

1.安装扩展

cd toyrpc
pip install -r requirements.txt
cd src/RPC-CPP/cc
python setup.py build_ext --inplace

2.配置环境

Windows:

$ export PYTHONPATH="\path\to\your\tpc-project\src;$PYTHONPATH"

Linux:

export PYTHONPATH="/your/path:$PYTHONPATH"
$ source ~/.bashrc

3.启动服务端

cd src/tests/example-provider-consumer
python provider.py

4.启动客户端

cd src/tests/example-provider-consumer
python consumer.py

Python-Java子框架接口描述语言(IDL)使用说明

本节介绍如何使用Python-Java子框架内置IDL编译器,生成跨语言RPC客户端代码。

1.IDL语法示例

service CalculatorService {
    version "1.0.0"
    description "A simple calculator service"
    
    method add(a: float, b: float) -> float {
        description "Add two numbers"
    }
    
    method factorial(n: int) -> int {
        description "Calculate factorial"
    }
    
    method hello(name: string = "World") -> string {
        description "Generate greeting with default parameter"
    }
}

2.使用IDL编译器生成客户端代码

2.1 生成Java客户端服务代码

python ./examples/test_idl_generate_java_client.py

该IDL会生成Java语言EquationService服务,运行命令后,将执行下面四个步骤:

  • 创建IDL编译器实例
  • 解析定义的IDL文件
  • 生成Java客户端代码
  • 将代码保存到指定目录(默认为 examples/generated/)

2.2 生成多语言客户端代码

python ./examples/example_idl.py

运行命令后,将执行下面四个步骤:

  • 创建IDL编译器实例
  • 解析service.idl文件
  • 同时生成Python与Java客户端代码
  • 将代码保存到指定目录(默认为 examples/generated/)

3.跨语言调用测试

3.1 Python客户端测试

首先,确保服务端已启动,用于注册服务:

python ./examples/example_service.py

然后运行IDL编译器:

python ./examples/example_idl.py

最后运行各类服务测试脚本:

  • 基本数学运算测试
    python ./examples/idl_test/python/test_cal.py
  • 高级数学运算测试
    python ./examples/idl_test/python/test_eq.py
  • 工具类服务测试
    python ./examples/idl_test/python/test_ut.py

3.2 Java客户端测试

首先,启动服务端并注册服务:

python examples/calculator_service.py

其次,执行IDL编译器生成Java客户端:

python examples/idl_demo.py    

然后,编译生成的客户端代码:

javac ./examples/generated/CalculatorServiceClient.java
javac ./examples/generated/EquationServiceClient.java
javac ./examples/generated/UtilityServiceClient.java

然后,编译测试代码:

javac -cp "./examples/generated" ./examples/idl_test/java/test_cal.java
javac -cp "./examples/generated" ./examples/idl_test/java/test_eq.java
javac -cp "./examples/generated" ./examples/idl_test/java/test_ut.java

最后,运行各类服务测试脚本:

  • 基本数学运算测试
    java -cp "examples/idl_test/java/;./examples/generated" test_cal
  • 高级数学运算测试
    java -cp "examples/idl_test/java/;./examples/generated" test_eq
  • 工具类服务测试
    java -cp "examples/idl_test/java/;./examples/generated" test_ut

性能分析

性能特点

  • 并发连接能力
    • 支持多个客户端同时发起连接请求,服务端侧通过线程池机制并发处理,避免单连接阻塞整个服务流程。连接生命周期由服务端统一管理,具备基础连接回收能力。
  • 消息大小限制
    • 每次请求或响应的最大消息体限制为1MB,用于防止超大消息造成内存溢出或序列化耗时过长。该限制时服务端和客户端双方均默认约定的边界,在消息解码阶段进行检查与拒绝处理。
  • 连接池复用机制(服务器端)
    • 服务端对接入连接可进行复用处理,避免在每次请求时频繁建立和断开连接,提升系统整体的I/O效率。此机制尤其适用于客户端短时高频请求场景,减少握手成本。
  • 序列化方式
    • 框架采用JSON格式作为消息序列化协议,具有良好的可读性和语言兼容性,方便调试和与多语言客户端对接。由于使用纯文本格式,性能上相较于二进制协议有一定损耗,但换来了更高的可移植性和易调试性。

跨语言调用性能增益(Python-C++)

编译型语言对比解释型语言,天然具有性能优势。相同时间复杂度的算法,C++版本要比Python版本快十倍至百倍不等。因此,现代RPC架构中,对于计算密集型的服务,Server提供Client C++服务,允许Client以Python或其他解释型语言(user-friendly)调用,从而达到用户友好&极致性能的效果。

对于计算密集型任务,toyRPC在Server端提供任务的C++高性能实现,支持Client在python程序中调用Server端的C++函数。 toyRPC目前实现了getNPrimes(获取前N个质数),getNFibonacci(获取前N个斐布拉契数),matmul(矩阵乘法),以下为跨语言调用相较于单语言调用(Server,Client统一使用Python)带来的性能收益。横轴为算法规模$N$,纵轴为服务执行时间(单位为毫秒)。三个任务均达到了好的性能收益,特别在矩阵乘法任务中,$N = 256$时,获得了平均600+倍的性能收益。

primes_performance.png fibonacci_performance.png matrix_performance.png

当前限制

  • 网络协议限制
    • 当前系统通信基于TCP通信协议实现,仅支持面向连接的可靠传输,不支持基于报文的UDP协议。因而不适合对延迟极端敏感、或对包丢失容忍度低的应用场景。
  • 安全性机制缺失
    • 当前版本未集成任何加密传输或身份认证机制,数据在TCP层面明文传输,容易遭受中间人攻击或监听。
    • 服务端不对接入连接做权限验证,存在非法请求接入风险。
    • 安全特性可通过在应用层叠加认证逻辑或或部署于可信网络环境中临时缓解。
  • 缺乏负载均衡能力
    • 当前系统仅支持单服务实例运行,不具备服务集群与调度能力。
    • 客户端无法自动根据服务负载或地址列表进行服务发现和动态路由。
    • 在高并发或高可用场景中,该特性缺失可能导致性能瓶颈或单点故障。
  • 服务注册信息不持久化
    • 所有服务注册信息仅保存在内存中,服务端重启后将全部丢失。
    • 系统目前不具备持久化机制(如写入本地文件或注册中心),因此无法支持长期运行状态下的动态注册于恢复能力。

项目总结

本项目以“从零构建 RPC 框架”为目标,系统实现了两个RPC子框架,构建了一个具备跨语言调用能力的远程过程调用系统,涵盖了服务注册、序列化传输、协议解析、并发处理、IDL 编译等核心模块,最终形成了具备工程完整度和教学演示价值的通用型 RPC 框架。

项目成果概括

  • 成功构建了两个基于Socket的自定义RPC框架,支持请求-响应模型多线程并发处理多语言客户端交互
  • 实现了*平台化服务管理机制,支持服务的动态注册状态监控调用统计
  • 构建了具有较好可扩展性的IDL编译器,可将接口描述文件自动转换为不同语言客户端调用代码,验证了跨语言调用的可行性。
  • 提供了完整的客户端测试脚本服务端示例IDL生成逻辑,验证了系统的正确性、并发性与可移植性。

    技术亮点与工程积累

  • 采用自定义协议格式(固定二进制头 + JSON 消息体),兼顾了性能与可读性,为后续协议升级留有扩展空间。
  • 服务端采用线程池处理模型,客户端设计了异步接收 + 请求映射机制,提升了整体并发处理能力与系统稳定性。
  • 通过语言抽象层与跨语言适配器设计,实现了对 Python 与 Java 、Python与C++两种模式的支持,并通过类型映射与序列化解耦,确保系统良好的语言扩展能力。
  • IDL 编译器采用模块化解析 + 模板代码生成机制,为后续引入更多语言支持提供了清晰路径。
  • 构建过程完全基于原生 Python,无依赖第三方 RPC 框架,突出原理教学性与系统可控性。

    工程价值与适用场景

    该框架适用于以下典型场景:
  • 用作教学案例,帮助学生理解分布式系统中 RPC 的核心组成与实现流程。
  • 用于中小型系统中轻量级服务通信需求,例如内部模块间通信或工具型服务调用。
  • 作为构建高层服务治理系统的基础构件,可与注册中心、服务网关、安全认证模块集成,逐步演进为完整的服务框架。

    当前局限与未来展望

    尽管本项目实现了基础 RPC 能力,但当前仍存在以下局限性
  • 缺乏对网络安全机制(如 TLS、身份认证)的支持。
  • 不具备服务发现与负载均衡能力,客户端路由逻辑较为静态。
  • 服务注册信息不具备持久化机制,系统重启后无法恢复注册状态。
  • 消息传输协议目前仍依赖文本 JSON,序列化性能仍有优化空间。

未来的改进方向包括:

  • 将两个不同的框架合并,使结构更加清晰简洁。
  • 引入统一注册中心(如 Redis、ZooKeeper 或自定义实现),实现服务持久化与动态发现。
  • 实现 TLS 加密通信与双向身份认证,提升传输层安全性。
  • 拓展对更多语言的 IDL 编译支持,丰富跨语言生态。
  • 支持服务端集群部署与客户端负载均衡策略,提高系统可用性与扩展性。
  • 引入二进制高性能序列化协议(如 Protobuf、MessagePack)以提升处理效率。

总体而言,本项目不仅完成了一个具备完整通信与调度能力的 RPC 系统,两个不同的子框架分别支持Python-Java、Python-C++两种跨语言交互,更在模块设计、协议构建、跨语言支持等方面进行了系统性探索,积累了宝贵的工程经验,为进一步开发更复杂、高性能、高可用的分布式系统奠定了坚实基础。

关于

A minimalist cross-language RPC framework supporting Python <-> C++ interoperability.

906.0 KB
邀请码
    Gitlink(确实开源)
  • 加入我们
  • 官网邮箱:gitlink@ccf.org.cn
  • QQ群
  • QQ群
  • 公众号
  • 公众号

©Copyright 2023 CCF 开源发展委员会
Powered by Trustie& IntelliDE 京ICP备13000930号