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/ 目录(测试用例设计 + 测试报告) |
测试总结与优化建议:
- 后续可补充 WSL/Linux 本地压测,消除公网延迟对性能数据的干扰,获取更准确的服务端吞吐量上限
- 可增加 JMeter 集成,利用其图形化报告和分布式压测能力
- 异常测试可进一步覆盖断网重连、服务端 OOM、TCP 半开连接等场景
- 建议对性能测试中 P95/P99 延迟偏高的问题做 profiling,定位是客户端 Python GIL 还是服务端 EventLoop 排队
本报告部分内容由 AI 辅助生成。测试数据均来源于实际执行结果,日志文件存放于 Auto_test/test/log/ 目录。