gRPC vs REST 性能对比:服务间通信协议的科学选型
gRPC vs REST 性能对比服务间通信协议的科学选型一、不要用谁流行来决定通信协议实习生选型时最常见的理由大家都用 REST我们也用 REST或者Google 都用 gRPC我们跟上。但技术选型永远要用数据说话尤其是服务间通信协议——它直接影响延迟、吞吐量和序列化开销这三个核心指标。我在刷题系统改造中做了 gRPC 和 REST 的对比基准测试。Judge Worker 和 Scheduler 的通信包含了大量结构化数据代码文本、测试用例列表、评测结果对象。在最坏情况下单次 RPC 的 payload 可达 500KB。这个场景下协议选择的差异被放大到了肉眼可见的量级。gRPC 快不是因为它是 Google 做的而是因为每一个环节都做了针对性优化。下面我们逐层剖析。二、序列化JSON 的自描述税和 Protobuf 的编号寻址JSON 的序列化结果中大量字节花在了描述这段数据叫什么上。一个评测结果对象{submission_id:abc123,status:accepted,time_ms:45,memory_kb:8192,test_cases_passed:10}这条 JSON 中submission_id占了 14 字节status占 7 字节time_ms占 8 字节——字段名本身占了 40% 以上的体积。当 payload 包含 50 个测试用例时每个用例都重复一遍input、expected、actual字段名浪费急剧放大。Protobuf 的做法是给每个字段一个数字编号传输时只携带编号和值接收方用 .proto 定义文件还原字段名syntax proto3; message JudgeResult { string submission_id 1; string status 2; int32 time_ms 3; int32 memory_kb 4; int32 test_cases_passed 5; int32 test_cases_total 6; repeated TestCaseResult test_cases 7; } message TestCaseResult { int32 index 1; string status 2; int32 time_ms 3; string error_message 4; }import time import json import struct from dataclasses import dataclass from typing import List, Tuple dataclass class TestCaseResult: 测试用例评测结果 index: int status: str # passed | failed | timeout time_ms: int error_message: str dataclass class JudgeResult: 评测结果 submission_id: str status: str # accepted | wrong_answer | runtime_error time_ms: int memory_kb: int test_cases_passed: int test_cases_total: int test_cases: List[TestCaseResult] class SimpleProtobufEncoder: 简化的 Protobuf 编码器仅用于演示原理 真实项目中请使用 google.protobuf 或更好的库。 这里手动实现是为了展示 Varint 编码和 wire type 的核心思想。 Protobuf 每条字段编码格式 [tag] [length/data] 其中 tag (field_number 3) | wire_type Wire Types: 0: Varint (int32, int64, uint32, bool, enum) 1: 64-bit (fixed64, double) 2: Length-delimited (string, bytes, embedded messages) 5: 32-bit (fixed32, float) staticmethod def encode_varint(value: int) - bytes: Varint 编码用最少的字节表示整数 每个字节的最高位MSB是是否还有后续字节的标志 - 1: 还有后续字节 - 0: 这是最后一个字节 示例300 的 Varint 编码 二进制1001011009 bits → 需要 2 字节 字节1: 10101100 (MSB1, 后7位0101100) 字节2: 00000010 (MSB0, 后7位0000010) → 0xAC 0x02仅 2 字节而 JSON 字符串 300 占 3 字节 result [] while value 0x7F: # MSB 置 1表示还有更多字节 result.append((value 0x7F) | 0x80) value 7 result.append(value 0x7F) return bytes(result) staticmethod def encode_tag(field_number: int, wire_type: int) - bytes: 编码 field tag (field_number 3) | wire_type return SimpleProtobufEncoder.encode_varint((field_number 3) | wire_type) staticmethod def encode_string(field_number: int, value: str) - bytes: 编码字符串字段 data value.encode(utf-8) tag SimpleProtobufEncoder.encode_tag(field_number, 2) # wire_type2 length SimpleProtobufEncoder.encode_varint(len(data)) return tag length data staticmethod def encode_int32(field_number: int, value: int) - bytes: 编码 int32 字段 tag SimpleProtobufEncoder.encode_tag(field_number, 0) # wire_type0 return tag SimpleProtobufEncoder.encode_varint(value) classmethod def encode_judge_result(cls, result: JudgeResult) - bytes: 编码完整的 JudgeResult 消息 字段编号对应 .proto 定义中的编号。 测试用例列表作为 repeated 字段逐个编码后拼接。 buf bytearray() # 字段 1: submission_id (string) buf.extend(cls.encode_string(1, result.submission_id)) # 字段 2: status (string) buf.extend(cls.encode_string(2, result.status)) # 字段 3: time_ms (int32) buf.extend(cls.encode_int32(3, result.time_ms)) # 字段 4: memory_kb (int32) buf.extend(cls.encode_int32(4, result.memory_kb)) # 字段 5: test_cases_passed (int32) buf.extend(cls.encode_int32(5, result.test_cases_passed)) # 字段 6: test_cases_total (int32) buf.extend(cls.encode_int32(6, result.test_cases_total)) # 字段 7: test_cases (repeated embedded message) for tc in result.test_cases: tc_buf bytearray() tc_buf.extend(cls.encode_int32(1, tc.index)) tc_buf.extend(cls.encode_string(2, tc.status)) tc_buf.extend(cls.encode_int32(3, tc.time_ms)) if tc.error_message: tc_buf.extend(cls.encode_string(4, tc.error_message)) # 嵌入消息wire_type2length子消息长度 tag cls.encode_tag(7, 2) length cls.encode_varint(len(tc_buf)) buf.extend(tag length tc_buf) return bytes(buf) def benchmark_serialization(n_iterations: int 10000): 对比 JSON 和 Protobuf 的序列化性能和体积 测试场景50 个测试用例的完整评测结果 # 构造测试数据 test_cases [ TestCaseResult( indexi, statuspassed if i % 3 ! 0 else failed, time_ms10 i * 2, error_messageindex out of bounds if i % 3 0 else ) for i in range(50) ] result JudgeResult( submission_idsub_abc123def456, statuswrong_answer, time_ms1500, memory_kb16384, test_cases_passed33, test_cases_total50, test_casestest_cases ) # JSON 序列化 json_str json.dumps({ submission_id: result.submission_id, status: result.status, time_ms: result.time_ms, memory_kb: result.memory_kb, test_cases_passed: result.test_cases_passed, test_cases_total: result.test_cases_total, test_cases: [ { index: tc.index, status: tc.status, time_ms: tc.time_ms, error_message: tc.error_message } for tc in test_cases ] }) json_bytes json_str.encode(utf-8) # Protobuf 序列化 proto_bytes SimpleProtobufEncoder.encode_judge_result(result) # JSON 序列化性能 start time.perf_counter() for _ in range(n_iterations): json.dumps({ submission_id: result.submission_id, status: result.status, time_ms: result.time_ms, memory_kb: result.memory_kb, test_cases_passed: result.test_cases_passed, test_cases_total: result.test_cases_total, test_cases: [ { index: tc.index, status: tc.status, time_ms: tc.time_ms, error_message: tc.error_message } for tc in test_cases ] }).encode(utf-8) json_elapsed time.perf_counter() - start # Protobuf 序列化性能 start time.perf_counter() for _ in range(n_iterations): SimpleProtobufEncoder.encode_judge_result(result) proto_elapsed time.perf_counter() - start # 报告 print( * 60) print(序列化性能对比JSON vs Protobuf) print( * 60) print(f测试数据{len(test_cases)} 个测试用例) print() print(fJSON 体积: {len(json_bytes):,} bytes) print(fProtobuf 体积: {len(proto_bytes):,} bytes) print(f压缩率: {(1 - len(proto_bytes) / len(json_bytes)) * 100:.1f}%) print() print(fJSON 序列化: {json_elapsed:.4f}s ({n_iterations} 次)) print(fProtobuf 序列化: {proto_elapsed:.4f}s ({n_iterations} 次)) print(f速度比: {json_elapsed / proto_elapsed:.1f}x) return { json_size: len(json_bytes), proto_size: len(proto_bytes), json_time: json_elapsed, proto_time: proto_elapsed, compression_ratio: (1 - len(proto_bytes) / len(json_bytes)) * 100 } if __name__ __main__: benchmark_serialization()在上面的基准测试中50 个测试用例的评测结果Protobuf 的体积大约只有 JSON 的 40%-55%。小包场景如健康检查差距不大但**大数据量场景评测结果、批量题目推送**差异显著。三、HTTP/2 多路复用为什么一个连接够用REST 在 HTTP/1.1 下通常需要连接池——每个请求占用一个 TCP 连接或通过 keep-alive 复用但仍是串行的请求-响应模式。高并发场景下连接池的开销TCP 握手、TLS 握手显著。gRPC 基于 HTTP/2天然支持多路复用一个 TCP 连接上可以同时进行多个请求-响应流。这消除了连接池管理的复杂性也大幅减少了内存占用。# HTTP/1.1 REST 的连接管理开销示意 # 100 个并发请求需要 100 个连接或连接池复用 # 每个连接的开销 # - TCP 三次握手1.5 个 RTT # - TLS 四次握手2 个 RTT # - 内存每个连接 ~10KB缓冲区 状态 # HTTP/2 gRPC 的连接开销示意 # 100 个并发请求共享 1 个 TCP 连接 # 连接开销仅 1 次 TCP 1 次 TLS # 内存~100KB共享 流状态vs HTTP/1.1 的 ~1MB这个差异在 Judge Scheduler 分发任务给 Worker 集群时最明显Scheduler 需要同时向 20 个 Worker 分发评测任务。如果用 REST需要维护 20 个连接或连接池用 gRPC1 个连接就够了。四、选型决策框架不是非黑即白场景推荐原因浏览器直接调用REST浏览器对 Protobuf 原生支持有限后端微服务间调用gRPC低延迟、强类型、二进制效率第三方 API 公开REST生态兼容性好调试工具丰富流式数据传输gRPC原生支持双向流快速原型开发REST无需 .proto 定义curl 直接调试决策框架不是gRPC 全面替代 REST而是根据调用链路选择协议。在刷题系统中前端 ↔ API GatewayREST浏览器兼容API Gateway ↔ Judge SchedulergRPC低延迟、高吞吐Judge Scheduler ↔ Judge WorkergRPC流式传输评测结果五、总结数据而非口号gRPC 和 REST 的对比本质上不是谁更好而是在什么场景下哪个协议在序列化效率、传输延迟和生态兼容性上达到最优平衡。测试数据表明在 50 个测试用例的评测结果场景下Protobuf 比 JSON 体积小 45%-55%序列化速度快约 3-5 倍。这不是夸大宣传而是基于 Varint 编码、字段编号寻址和 HTTP/2 多路复用的工程优化。选型时三个验证步骤用你的实际 payload 大小做基准测试测量序列化/反序列化的 CPU 开销在目标并发量下对比端到端延迟做完这三步答案自然就有了。

相关新闻

RAG驱动的智能答疑系统:让AI“读懂“你的问题再回答

RAG驱动的智能答疑系统:让AI“读懂“你的问题再回答

RAG驱动的智能答疑系统:让AI"读懂"你的问题再回答 一、"为什么我的代码超时了?"——这个问题的答案不在模型参数里 用户提交了一段代码,输出是 "Time Limit Exceeded",他问系统:"为…

2026/7/16 17:46:11 阅读更多 →
GoLand 2025 安装配置全指南:适配 Go 1.23 与 go.work 工作区

GoLand 2025 安装配置全指南:适配 Go 1.23 与 go.work 工作区

1. 为什么是 GoLand,而不是 VS Code 或其他编辑器?——从真实开发场景出发的选型逻辑 GoLand 是 JetBrains 官方推出的 Go 语言专属 IDE,不是“又一个代码编辑器”,而是一套深度理解 Go 语言语义、运行时机制和工程实践的智能开发…

2026/7/16 17:44:10 阅读更多 →
从实验室到生产线:USAF1951分辨率板在光学系统全周期评估中的实战指南

从实验室到生产线:USAF1951分辨率板在光学系统全周期评估中的实战指南

1. USAF1951分辨率板:光学工程师的"标尺" 第一次接触USAF1951分辨率板时,我正为一个工业相机选型项目头疼——三家供应商的镜头参数表上都写着"200万像素",但实际成像效果天差地别。直到实验室老师傅扔给我一块带黑白条纹…

2026/7/16 17:44:10 阅读更多 →

最新新闻

从数据框到Tibble:3个关键差异让你的R代码更优雅、更健壮

从数据框到Tibble:3个关键差异让你的R代码更优雅、更健壮

从数据框到Tibble:3个关键差异让你的R代码更优雅、更健壮 【免费下载链接】tibble A modern re-imagining of the data frame 项目地址: https://gitcode.com/gh_mirrors/ti/tibble Tibble是R语言中对传统数据框(data frame)的现代化重…

2026/7/16 18:40:28 阅读更多 →
运算放大器输出电压反向问题解析与解决方案

运算放大器输出电压反向问题解析与解决方案

1. 运算放大器输出电压反向问题解析运算放大器(Op-Amp)在实际电路设计中,输出电压反向是一个常见但容易被忽视的问题。当输出信号相位与预期相反时,往往会导致整个系统功能异常。这种现象在反相放大器电路中尤为典型,但…

2026/7/16 18:38:27 阅读更多 →
巴菲特警告+DRAM涨价遭抵制,存储芯片股本周剧烈震荡,行业周期隐忧浮现

巴菲特警告+DRAM涨价遭抵制,存储芯片股本周剧烈震荡,行业周期隐忧浮现

本周存储芯片股遭遇剧烈震荡,多重利空信号叠加,此前因AI需求驱动的涨势骤然承压。巴菲特警告、DRAM涨价遭抵制等因素,让市场对存储板块未来走势充满担忧。股价暴跌:巴菲特言论引发情绪逆转周三,美光、闪迪、西部数据、…

2026/7/16 18:36:26 阅读更多 →
orin部署tensorrt、cuda、cudnn、pytorch、onnx\opencv、JetPack

orin部署tensorrt、cuda、cudnn、pytorch、onnx\opencv、JetPack

绝大部分参考https://blog.csdn.net/qq_41336087/article/details/129661850 安装完JetPack大部分环境就安装好了 非orin可以参考https://blog.csdn.net/JineD/article/details/131201121 报错显卡驱动安装535没法安装、原始是和l4t-cuda的部分文件冲突Options marked [*] prod…

2026/7/16 18:36:26 阅读更多 →
3步轻松搞定网页转PDF:用url-to-pdf-api实现高效文档转换

3步轻松搞定网页转PDF:用url-to-pdf-api实现高效文档转换

3步轻松搞定网页转PDF:用url-to-pdf-api实现高效文档转换 【免费下载链接】url-to-pdf-api Web page PDF/PNG rendering done right. Self-hosted service for rendering receipts, invoices, or any content. 项目地址: https://gitcode.com/gh_mirrors/ur/url-t…

2026/7/16 18:34:25 阅读更多 →
如何在浏览器与Node.js中无缝使用bytebuffer.js?3分钟快速上手教程

如何在浏览器与Node.js中无缝使用bytebuffer.js?3分钟快速上手教程

如何在浏览器与Node.js中无缝使用bytebuffer.js?3分钟快速上手教程 【免费下载链接】bytebuffer.js A fast and complete ByteBuffer implementation using either ArrayBuffers in the browser or Buffers under node.js. 项目地址: https://gitcode.com/gh_mirr…

2026/7/16 18:34:25 阅读更多 →

日新闻

HarmonyOs应用《重要日》开发第6篇 - 数据持久化存储

HarmonyOs应用《重要日》开发第6篇 - 数据持久化存储

本篇深入剖析 ImportantDays 项目的数据持久化方案——基于 HarmonyOS ArkData 模块的 Preferences 轻量级存储,以及 PreferenceUtil 工具类的单例封装。一、HarmonyOS 数据存储方案对比 HarmonyOS 提供了多种数据存储方案:方案适用场景特点Preferences轻…

2026/7/16 0:08:27 阅读更多 →
Python实现跨境电商商品图批量翻译教程

Python实现跨境电商商品图批量翻译教程

一、问题引入做跨境电商的卖家朋友,你是否遇到过这样的困扰?每次上架新品到亚马逊、Shopee或Lazada等平台,都需要处理大量商品图片的多语言版本。比如上架200款衣服,每款需要翻译成英语、日语、韩语等5种语言,这意味着…

2026/7/16 0:08:27 阅读更多 →
鸿蒙 7 新特性实战①:从 0 到 1 掌握 @kit 标准导入规范

鸿蒙 7 新特性实战①:从 0 到 1 掌握 @kit 标准导入规范

从鸿蒙 7(HarmonyOS NEXT)开始,官方全面完成了从 ohos.* 零散模块到 kit.* 领域套件的体系重构。对开发者来说,第一道门槛不是 API 用法变化,而是统一的导入规范——旧体系默认导入、解构导入混用的混乱局面被彻底终结…

2026/7/16 0:10:29 阅读更多 →

周新闻

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨 在一个阳光明媚的上午,互联网大厂的面试官坐在桌前,准备迎接他的面试候选人——燕双非,一个以搞笑和幽默著称的程序员。第一轮提问 面试官:燕双非,作…

2026/7/15 21:09:01 阅读更多 →
车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估

车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估

车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估在智能驾驶和车联网技术快速发展的今天,车载以太网作为新一代车载网络的核心传输技术,其物理层性能直接决定了数据传输的可靠性和稳定性。1000BASE-T1作为当前主流的…

2026/7/15 19:42:20 阅读更多 →
VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战,5步完成Keil工程转换

VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战,5步完成Keil工程转换

VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战指南嵌入式开发领域正经历一场工具链的静默革命。当传统Keil用户首次打开VSCode的扩展市场搜索EIDE时,往往会惊讶于这个看似简单的插件竟能重构十余年的开发习惯。本文将揭示如何用五个精准步骤&#xff0…

2026/7/15 17:52:08 阅读更多 →

月新闻