【JSON-RPC远程过程调用组件库】测试报告

RPC 框架测试报告


一、项目背景

本项目是一个基于 C++ 实现的轻量级 RPC(远程过程调用)框架,旨在解决分布式系统中服务间通信的复杂性。框架提供三大核心能力:基础 RPC 远程调用 (同步/异步/回调三种模式)、基于注册中心的服务发现 (支持服务注册、发现、上线/下线通知)、主题发布订阅(1:N 消息推送)。底层基于 Muduo 高性能网络库,自定义 TLV 应用层协议解决 TCP 粘包问题,使用 JSON 作为序列化格式。项目为学习型框架,核心价值在于从零理解 RPC 框架的协议编解码、请求路由分发、服务发现机制和发布订阅设计。


二、项目简介

本 RPC 框架包含以下核心功能模块:

  • 基础 RPC 模块:客户端通过 TCP 连接直连服务端,发送 JSON 请求调用注册的远程方法(如 Add),支持同步阻塞、异步 Future、回调三种调用模式。服务端通过 RpcRouter 进行方法路由、参数类型校验和回调执行。

  • 服务注册与发现模块:RegistryServer 作为注册中心,管理服务提供者的注册/注销,维护方法与主机列表的映射。客户端通过 Discoverer 进行服务发现,支持本地缓存和上线/下线通知推送。

  • 发布订阅模块:TopicServer 管理主题的创建/删除/订阅/取消/发布。发布者发布消息后,服务端通过 Topic::pushMessage 遍历订阅者集合并主动推送消息(1:N 推模式)。

  • 协议层 :自定义 LVProtocol 二进制协议,格式为 | 4B len | 4B mtype | 4B idLen | id 字符串 | JSON body |,所有整数字段使用网络字节序。


三、测试计划

3.1 测试策略

采用黑盒集成测试策略。测试客户端基于 Python 实现,完全独立于 C++ 框架代码,通过 TCP 套接字直接构造 LVProtocol 二进制数据包与服务端交互。测试覆盖三个核心模块的 Happy Path、异常边界、并发场景和性能压测。

3.2 测试环境

组件 说明
服务端 Ubuntu 云服务器,C++ (g++ 11 + Muduo + jsoncpp)
测试客户端 Windows 10,Python 3.x
通信方式 公网 TCP,自定义 LVProtocol 二进制协议
RpcServer 端口 9090,注册 Add(num1:int, num2:int) → int
RpcServer(registry) 端口 9090,启用服务注册,连接注册中心
RegistryServer 端口 8080,服务注册与发现
TopicServer 端口 7070,主题发布订阅

3.3 提测方式

分模块独立测试:基础 RPC → 发布订阅 → 服务发现 → 异常边界 → 性能压测。各模块用例独立,最后执行全量回归确保无兼容性问题。


四、测试工具

工具 用途
Python + pytest 自动化功能测试,实现 LVProtocol 协议封包/解包
Python + ThreadPoolExecutor 性能压测,多线程并发
自定义 protocol.py TLV 协议二进制封包/解包,对应 C++ 源码 LVProtocol 类
Git 版本管理,代码修改与同步
日志文件 测试结果持久化至 test/log/ 目录

五、测试类型与覆盖情况

测试类型 覆盖范围 发现问题数量 备注
功能测试 基础RPC、发布订阅、服务发现三大模块核心功能 0 验证功能逻辑、错误码返回、连接关闭等
异常测试 非法参数、非法JSON、非法mtype、超大body 0 验证服务端容错能力,无崩溃
边界测试 空body、len字段篡改、>64KB消息 0 验证maxDataSize保护和缓冲区处理
并发测试 多线程并发调用、粘包场景、连接池复用 0 验证线程安全与粘包拆分
性能测试 5档递增压力(10~200线程 / 1000~10000请求) 0 零错误,吞吐量天花板~320 req/s

六、功能测试

6.1 基础 RPC 模块

用例 ID 用例名称 测试步骤 预期结果 执行结果
TC-RPC-001 正常RPC调用 发送 Add(11,22) 请求 rcode=0, result=33 PASS
TC-RPC-002 参数类型错误 num1 传字符串 "hello" rcode=4 (INVALID_PARAM) PASS
TC-RPC-003 参数缺失 只传 num1,不传 num2 rcode=4 (INVALID_PARAM) PASS
TC-RPC-004 调用不存在方法 调用 Multiply rcode=5 (NOT_FOUND_SERVICE) PASS
TC-RPC-005 非法JSON body body 为截断的JSON字符串 连接被服务端关闭 PASS
TC-RPC-006 请求-响应ID匹配 同一连接发送3个不同ID请求 3个响应ID与请求一一对应 PASS
TC-RPC-007 连续多次调用 同一连接连续5次Add 5次全部返回正确结果 PASS
TC-RPC-008 并发多连接 3个并发连接各发2次Add 6次全部成功,无串扰 PASS
TC-RPC-009 粘包场景 快速连发3条请求,TCP合并 3条全部正常响应 PASS

测试结果:9/9 全部通过,零失败。
测试结果摘录(点击展开)

TC-RPC-001: 正常RPC调用 Add(11,22) → 33

json 复制代码
{
  "mtype": 3, "mtype_name": "RSP_R",
  "rcode": 0,
  "body": { "rcode": 0, "result": 33 }
}

来源: test/log/TC-RPC-001-20260510.txt

TC-RPC-004: 未知方法 Multiply → rcode=5 (NOT_FOUND_SERVICE)

json 复制代码
{
  "mtype": 3, "mtype_name": "RSP_R",
  "rcode": 5,
  "body": { "rcode": 5, "result": null }
}

来源: test/log/TC-RPC-004-20260510.txt

6.2 发布订阅模块

用例 ID 用例名称 测试步骤 预期结果 执行结果
TC-PUB-001 创建主题 创建主题 "news" rcode=0 PASS
TC-PUB-002 重复创建 同一连接创建两次 "news" 两次均返回 rcode=0 PASS
TC-PUB-003 订阅主题 创建 "news" 后订阅 rcode=0 PASS
TC-PUB-004 发布→订阅者接收 发布者发消息,检查订阅者 订阅者收到 REQ_TOPIC 推送,内容一致 PASS
TC-PUB-005 多订阅者1:N 3个订阅者,1个发布者 3个订阅者全部收到相同推送 PASS
TC-PUB-006 取消订阅 订阅后取消,再发布 原订阅者不再收到推送 PASS
TC-PUB-007 删除主题 删除后尝试订阅 rcode=7 (NOT_FOUND_TOPIC) PASS
TC-PUB-008 订阅不存在主题 直接订阅 "ghost_topic" rcode=7 (NOT_FOUND_TOPIC) PASS
TC-PUB-009 发布到无订阅者 创建后不订阅直接发布 rcode=0,服务端不崩溃 PASS
TC-PUB-010 订阅者断连 订阅者断开,发布者再发布 服务端不崩溃,正常返回 rcode=0 PASS
TC-PUB-011 非法操作类型 optype=99 rcode=6 (INVALID_OPTYPE) PASS

测试结果:11/11 全部通过,零失败。
测试结果摘录(点击展开)

TC-PUB-004: 发布消息 → 订阅者接收推送

发布者发布 "hello world" 到 "news" 主题 → 发布者收到 rcode=0,订阅者收到 REQ_TOPIC 推送:

json 复制代码
{
  "publish_response": {
    "mtype": 4, "mtype_name": "RSP_TOPIC",
    "rcode": 0
  },
  "push_message": {
    "mtype": 1,
    "body": {
      "optye": 4,
      "topic_key": "news",
      "topic_msg": "hello world"
    }
  }
}

来源: test/log/TC-PUB-004-20260510.txt

TC-PUB-011: 非法 optype=99 → rcode=6 (INVALID_OPTYPE)

json 复制代码
{
  "mtype": 4, "mtype_name": "RSP_TOPIC",
  "rcode": 6,
  "body": { "rcode": 6 }
}

来源: test/log/TC-PUB-011-20260510.txt

6.3 服务发现模块

用例 ID 用例名称 测试步骤 预期结果 执行结果
TC-SD-001 服务注册 向注册中心注册 Subtract 方法 rcode=0 PASS
TC-SD-002 服务发现 发现 Add 方法 host列表包含 9090 端口提供者 PASS
TC-SD-003 发现不存在服务 发现 NoSuchMethod rcode=5 (NOT_FOUND_SERVICE) PASS
TC-SD-004 服务上线通知 发现者先登记,提供者后注册 发现者收到 SERVICE_ONLINE 推送 PASS
TC-SD-005 服务下线通知 提供者断开连接 发现者收到 SERVICE_OFFLINE 推送 PASS
TC-SD-006 后注册通知已知发现者 发现者先登记,提供者后注册 发现者收到上线通知,提供者注册成功 PASS
TC-SD-007 多提供者同方法 两个提供者注册 Echo 发现返回两个 host PASS
TC-SD-008 提供者断连自动注销 提供者注册后断开,新发现者查询 rcode=5 (NOT_FOUND_SERVICE) PASS
TC-SD-009 发现者断连不影响 发现者断连,新发现者查询 仍能正常发现 Add PASS
TC-SD-010 端到端全流程 发现Add→获取地址→直连RPC Add(11,22)=33 PASS
TC-SD-011 非法操作类型 optype=99 rcode=6 (INVALID_OPTYPE) PASS

测试结果:11/11 全部通过,零失败。
测试结果摘录(点击展开)

TC-SD-010: 端到端全流程 --- 服务发现 → 直连 RPC → Add(11,22)=33

json 复制代码
{
  "discovery": {
    "mtype": 5, "mtype_name": "RSP_SERVICE",
    "rcode": 0,
    "body": {
      "host": [{ "host_ip": "127.0.0.1", "host_port": 9090 }],
      "method": "Add", "optye": 1, "rcode": 0
    }
  },
  "rpc_result": {
    "mtype": 3, "mtype_name": "RSP_R",
    "rcode": 0,
    "body": { "rcode": 0, "result": 33 }
  }
}

来源: test/log/TC-SD-010-20260510.txt

6.4 异常边界测试

用例 ID 用例名称 测试步骤 预期结果 执行结果
TC-EDGE-001 空body body为空字符串 连接关闭或返回解析错误 PASS
TC-EDGE-002 超大body >64KB 发送约70KB的消息 触发maxDataSize保护,连接关闭 PASS
TC-EDGE-003 len字段不匹配 len=20但实际body>200B 仅解析len指定的字节,不崩溃 PASS
TC-EDGE-004 非法mtype mtype=99 MessageFactory返回nullptr,连接关闭 PASS

测试结果:4/4 全部通过,零失败。
测试结果摘录(点击展开)

TC-EDGE-002: 超大 body >64KB → 触发 maxDataSize 保护

复制代码
连接被服务端关闭 (maxDataSize 保护触发),符合预期

来源: test/log/TC-EDGE-002-20260510.txt


七、自动化测试

7.1 测试脚本清单

脚本文件 模块 用例数 说明
test/protocol.py 协议层 --- LVProtocol 封包/解包,MType/RCode 枚举
test/test_rpc_basic.py 基础RPC 9 TC-RPC-001~009
test/test_topic_pubsub.py 发布订阅 11 TC-PUB-001~011
test/test_service_discovery.py 服务发现 11 TC-SD-001~011
test/test_edge_cases.py 异常边界 4 TC-EDGE-001~004
test/test_performance.py 性能压测 3场景 RPC/Topic/Discovery 并发压测

7.2 核心测试代码

LVProtocol 协议封包test/protocol.py):

python 复制代码
import struct

def pack(mtype: int, msg_id: str, body: str) -> bytes:
    """按 LVProtocol 格式打包: | len(4B) | mtype(4B) | idLen(4B) | id | body |"""
    id_bytes = msg_id.encode("utf-8")
    body_bytes = body.encode("utf-8")
    length = 4 + 4 + len(id_bytes) + len(body_bytes)
    header = struct.pack("!iii", length, mtype, len(id_bytes))
    return header + id_bytes + body_bytes

def unpack_first(data: bytes) -> tuple:
    """解出第一条完整消息,返回 (mtype, msg_id, body_str, total_msg_len)"""
    if len(data) < 12:
        return None
    length, mtype, id_len = struct.unpack("!iii", data[:12])
    total_msg_len = 4 + length
    if len(data) < total_msg_len:
        return None
    msg_id = data[12:12 + id_len].decode("utf-8")
    body_len = length - 4 - 4 - id_len
    body = data[12 + id_len:12 + id_len + body_len].decode("utf-8")
    return (mtype, msg_id, body, total_msg_len)

RPC 调用封装

python 复制代码
def rpc_request(method: str, params: dict, msg_id: str = None) -> bytes:
    if msg_id is None:
        msg_id = str(uuid.uuid4())
    body = json.dumps({"method": method, "parameters": params})
    return pack(0, msg_id, body)  # MType.REQ_R = 0

连接池长连接压测test/test_performance.py 核心片段):

python 复制代码
def worker_rpc(task_id: int):
    conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    conn.connect((HOST, RPC_PORT))
    recv_buf = b""
    while True:
        idx = get_next_index()
        start = time.perf_counter()
        conn.sendall(p.rpc_request("Add", {"num1": task_id * 100 + idx, "num2": idx}))
        while True:
            r = p.unpack_first(recv_buf)
            if r is not None:
                recv_buf = recv_buf[r[3]:]
                record_latency(idx, time.perf_counter() - start)
                break
            recv_buf += conn.recv(4096)
    conn.close()

八、性能测试

8.1 测试方案

采用递增压力模型,分 5 档从 10 线程逐步提升至 200 线程,总请求数从 1000 递增至 10000。前 2 档使用短连接(每次请求新建 TCP),后 3 档使用连接池长连接复用。覆盖三个场景:

场景 操作 端口
基础 RPC Add(num1, num2) 同步调用 9090
发布订阅 创建主题 + 发布消息 7070
服务发现 查询 Add 方法主机列表 8080

8.2 性能测试结果汇总

基础 RPC --- Add 方法递增压力

档位 并发 总请求 连接模式 吞吐量 P50 P95 P99 错误
基线 10 1,000 短连接 57.1 req/s 160.6ms 276.9ms 334.6ms 0
A档 50 3,000 短连接 211.9 req/s 171.7ms 554.2ms 1159.7ms 0
B档 100 5,000 短连接 209.5 req/s 232.0ms 1171.7ms 1277.6ms 0
B档(池) 100 5,000 连接池 327.9 req/s 87.6ms 1262.5ms 1347.9ms 0
C档 200 10,000 连接池 317.0 req/s 389.7ms 1665.1ms 1989.4ms 0

关键发现

  • 引入连接池后,同等 100 线程下 P50 从 232ms 降至 87.6ms(降幅 62%),吞吐量从 209.5 升至 327.9 req/s(升幅 57%)
  • 吞吐量天花板约 320 req/s,受限于公网延迟(~40ms RTT)+ Python GIL 客户端调度开销
  • 5 档递增压力、累计 24,000 次请求,零错误
  • 服务端 CPU 使用率始终低于 10%,框架自身无性能瓶颈

三场景对比(10线程基线)

场景 吞吐量 平均延迟 P95延迟 P99延迟
基础RPC 57.1 req/s 174.6ms 276.9ms 334.6ms
发布订阅 37.9 req/s 262.7ms 370.6ms 467.2ms
服务发现 60.2 req/s 165.3ms 218.5ms 267.0ms

服务发现最快(纯内存查表),发布订阅最慢(每次需 CREATE + PUBLISH 两次往返)。


九、项目测试 Bug 简述

9.1 Bug 优先级统计

本次测试共发现 0 个 Bug

  • P0(崩溃级):0 个
  • P1(严重级):0 个
  • P2/P3(一般/轻微级):0 个

9.2 Bug 详情列表

无。


十、遗留问题

本次测试未发现功能或性能层面的 Bug。以下为项目已知的架构层面待改进项(非本次测试发现的缺陷,而是框架设计阶段的已知局限):

编号 问题 优先级 说明
1 JSON序列化性能 P2 相比Protobuf有性能差距,大消息场景可成为瓶颈
2 无RPC超时机制 P1 future.get()无限阻塞,缺少超时保护
3 无心跳/健康检查 P1 注册中心仅被动感知TCP断开,无法检测静默故障
4 负载均衡仅轮询 P2 缺少加权、最少连接等策略
5 锁粒度可优化 P2 Dispatcher回调在锁内执行
6 无连接池 P2 客户端每次发现新地址建新连接
7 无流控/背压 P2 发布订阅场景无发送速率控制

十一、测试结论

整体测试结果 :本次项目测试通过。三大核心模块(基础RPC、发布订阅、服务发现)共 35 个功能 + 异常 + 边界用例全部通过,零失败。性能压测 5 档累计 24,000 次请求零错误,框架稳定可靠。

耗时统计:项目测试耗时约 2 小时(含功能测试 31 用例、异常边界 4 用例、性能压测 3 场景 5 档)。

项目补充信息

项目 信息
项目框架 C++ (g++ 11),Muduo 网络库,jsoncpp,自定义 LVProtocol 协议
代码仓库 https://gitee.com/BearOnToilet/json_rpc.git
测试代码 Auto_test/test/ 目录,Python 实现
测试文档 Auto_test/doc/ 目录(测试用例设计 + 测试报告)

测试总结与优化建议

  1. 后续可补充 WSL/Linux 本地压测,消除公网延迟对性能数据的干扰,获取更准确的服务端吞吐量上限
  2. 可增加 JMeter 集成,利用其图形化报告和分布式压测能力
  3. 异常测试可进一步覆盖断网重连、服务端 OOM、TCP 半开连接等场景
  4. 建议对性能测试中 P95/P99 延迟偏高的问题做 profiling,定位是客户端 Python GIL 还是服务端 EventLoop 排队

本报告部分内容由 AI 辅助生成。测试数据均来源于实际执行结果,日志文件存放于 Auto_test/test/log/ 目录。

相关推荐
星恒随风1 小时前
C语言链表详解:从单链表到双向链表
c语言·开发语言·链表
lsx2024061 小时前
《Foundation 均衡器:深入解析其工作原理与应用领域》
开发语言
常常有1 小时前
中间件与依赖系统:构建高效 Web 后端的双重利器
开发语言·python·中间件·fastapi
金玉满堂@bj1 小时前
Go 语言能做什么?
开发语言·后端·golang
ooseabiscuit1 小时前
Laravel6.x新特性全解析
java·开发语言·后端·mysql·spring
李日灐1 小时前
< 9 > Linux 进程:进程状态 + 进程切换 + 附带常用指令(jobs / fg / kill / ps)
linux·运维·服务器·后端·面试·进程状态
枕星而眠1 小时前
一篇吃透 C++ 核心基础:初始化、引用、指针、内联、重载、右值引用
开发语言·数据结构·c++·后端·visual studio
Royzst1 小时前
一、集合概述(前置基础)
开发语言·windows·python
Season4501 小时前
C/C++的类型转换
c语言·开发语言·c++