资讯动态

gRPC的介绍和使用学习

发布时间:2026/10/24 5:37:13 来源:尧图企业网站定制
gRPC 是一个由 Google 开源的高性能 RPC 框架核心思路是让你像调用本地方法一样去调用另一台机器上的服务。它默认使用Protocol Buffers作为接口定义语言和序列化格式底层强制运行在HTTP/2之上。核心架构Proto 契约 HTTP/2 传输gRPC 的“契约先行”模式是它区别于普通 REST 开发的关键。你先在.proto文件中定义服务接口和消息结构protoc编译器再据此生成客户端和服务端的存根代码。protobufsyntax proto3; service OrderService { rpc GetOrder (OrderRequest) returns (OrderResponse); } message OrderRequest { string order_id 1; } message OrderResponse { string order_id 1; string status 2; }这段定义就是服务双方必须遵守的强类型契约从源头上避免了接口不一致的问题。生成的客户端存根封装了网络通信细节服务端则负责实现接口中声明的具体逻辑。HTTP/2 为 gRPC 提供了三项关键能力多路复用单连接并行处理多个请求消除队头阻塞、头部压缩HPACK 显著减少重复头开销和二进制分帧替代 HTTP/1.1 的文本解析。四种通信模式gRPC 对流的支持是它最显著的能力差异点共有四种模式模式请求响应典型场景一元 RPC单条单条普通请求—响应最常用服务端流单条多条流实时推送、大结果集分块客户端流多条流单条批量上传、遥测数据上报双向流多条流多条流聊天、协同编辑、实时同步实践中一元 RPC 占据大多数使用场景服务端流次之双向流在实时性要求高的系统中才真正展现价值。gRPC 与 REST 的选型判断两者并非替代关系而是服务于不同场景。性能数据来自基准测试指标gRPC (HTTP/2 ProtoBuf)REST (HTTP/1.1 JSON)连接建立1 RTT3 RTT请求延迟2–3 ms8–12 ms吞吐量~1200 req/s~450 req/s在高并发场景下gRPC 的吞吐量优势可达42%延迟降低28%。Protobuf 的二进制体积通常只有等价 JSON 的1/3 左右序列化速度快3–5 倍。选择 gRPC 的场景数据中心内部或跨数据中心的微服务间通信追求低延迟和高吞吐。需要流式通信的系统实时推送、音视频信令、协同编辑。多语言技术栈环境利用代码生成保证跨语言契约一致。对带宽敏感的移动端或物联网后端受益于更小的载荷体积。继续选择 REST 的场景公开 API 或浏览器直连REST 的 HTTP/JSON 生态无需额外代理层即可工作。对可读性和调试便利性要求高于原始性能的场景。已有成熟的 REST 工具链和网关体系引入 gRPC 的边际收益不足以覆盖改造成本。快速入门五步走以一个 Python 一元 RPC 为例完整流程如下第 1 步定义.proto文件protobufsyntax proto3; package helloworld; message HelloRequest { string name 1; } message HelloReply { string message 1; } service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) {} }第 2 步安装工具bashpip install grpcio grpcio-tools第 3 步生成代码bashpython -m grpc_tools.protoc -I. --python_out. --grpc_python_out. helloworld.proto生成helloworld_pb2.py消息类和helloworld_pb2_grpc.py服务存根。第 4 步实现服务端pythonfrom concurrent import futures import grpc import helloworld_pb2 import helloworld_pb2_grpc class Greeter(helloworld_pb2_grpc.GreeterServicer): def SayHello(self, request, context): return helloworld_pb2.HelloReply(messagefHello, {request.name}) server grpc.server(futures.ThreadPoolExecutor(max_workers10)) helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server) server.add_insecure_port([::]:50051) server.start() server.wait_for_termination()第 5 步实现客户端并调用pythonimport grpc import helloworld_pb2 import helloworld_pb2_grpc with grpc.insecure_channel(localhost:50051) as channel: stub helloworld_pb2_grpc.GreeterStub(channel) response stub.SayHello(helloworld_pb2.HelloRequest(nameWorld)) print(response.message)其他语言的流程完全同构Go 使用protoc-gen-go和protoc-gen-go-grpc插件Java 使用 Maven 的protobuf-maven-pluginNode.js 使用grpc-tools。生产环境需要关注的三件事安全生产环境务必启用TLS 1.2服务间通信建议配置mTLS实现双向身份验证。grpc.CallCredentials等凭据机制仅在 TLS 通道上生效。性能调优核心原则是复用 channel 和 stub让 RPC 调用共享已有的 HTTP/2 连接而不是每次新建。高并发场景可引入 channel 池配合 keepalive 心跳防止空闲连接被中间设备断开。调优得当的情况下QPS 可以有数倍提升。浏览器兼容浏览器不原生支持直接发起 gRPC 调用。如果前端需要访问 gRPC 服务标准方案是部署gRPC-Web代理如 Envoy或使用gRPC-Gateway将 HTTP/JSON 请求转译为 gRPC 调用。这一层代理会带来额外的延迟开销约 15%–20%是选型时需要计入的成本。调试工具二进制协议使传统抓包工具难以直接解析日常调试可借助grpcurl命令行或grpcui图形界面。------------------------------------------------------------------------------------------------------------------实际使用一、protos/agent.protosyntax proto3; package agent; // 10-22 用户定名口径: 文件名/包名均不带数字 // Java 统一入口服务 → Python 引擎 的内网调用通道。方法随需求逐个加, 都放进同一个 service。 service Agent { // 千帆视觉探针: 图prompt 一发一答 (HTTP 对位件 POST /agent/v1/vision-probe) rpc VisionProbe(VisionProbeRequest) returns (VisionProbeResponse); } message VisionProbeRequest { bytes file 1; // 图片原始字节 (≤10MB, 与 HTTP 侧同钳; 空/超限INVALID_ARGUMENT) string file_name 2; // 仅用于按扩展名判 mime, 逻辑逐字沿用 agent_api.py 现件 string message 3; // prompt string report 4; // 同 HTTP 表单体, 不透明键不按值路由 (§5.1 口径, 故保 string 不缩 int32); 本通道引擎不落 stat, 只进日志对齐两通道形制 string key 5; // 可选, 同 HTTP 侧; 本通道仅随日志记录 string type 6; // 可选, 缺省 vision-probe (同 HTTP 侧) string model 7; // 可选, 空settings.LLM_VL_MODEL 兜底 (同 HTTP 侧) } message Usage { int64 input_tokens 1; int64 output_tokens 2; } message VisionProbeResponse { // 与 HTTP 侧 _R() 的 data 四键一一对位, 前端/Java 消费口径统一 string model 1; int64 elapsed_ms 2; string content 3; Usage usage 4; // 千帆返回缺失 usage 时置零值 (proto3 默认), 不报错 }依赖的库文件# gRPC 服务端 (10-22 首件 vision-probe, GRPC_DEV_PLAN S0; 镜像同件; grpcio-tools 仅开发机生成 stub, 不入本档) grpcio1.62Stub是gRPC 客户端桩负责把本地调用转换成远程 RPC 请求.venv\Scripts\python -c import grpc.venv\Scripts\python -m grpc_tools.protoc -I protos --python_outapis/grpc_gen --grpc_python_outapis/grpc_gen protos/agent.proto运行成功后通常会在apis/grpc_gen目录下生成类似这两个文件agent_pb2.py包含 proto 里定义的message、enum等数据结构。agent_pb2_grpc.py包含 gRPC 的Stub、Servicer、注册函数等。其中你前面问的stub就来自agent_pb2_grpc.py里生成的客户端类例如stub agent_pb2_grpc.AgentServiceStub(channel)这里的Stub是gRPC 客户端桩负责把本地调用转换成远程 RPC 请求。1. 全局链路谁调谁、走什么协议2. 一次 VisionProbe 调用的端到端时序protos/agent.proto// Java 统一入口服务 → Python 引擎 的内网调用通道。方法随需求逐个加, 都放进同一个 service。 service Agent { // 千帆视觉探针: 图prompt 一发一答 (HTTP 对位件 POST /agent/v1/vision-probe) rpc VisionProbe(VisionProbeRequest) returns (VisionProbeResponse); }apis/grpc_gen/ 两个文件class AgentServicer: Java 统一入口服务 → Python 引擎 的内网调用通道。方法随需求逐个加, 都放进同一个 service。 def VisionProbe(self, request, context): 千帆视觉探针: 图prompt 一发一答 (HTTP 对位件 POST /agent/v1/vision-probe) context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details(Method not implemented!) raise NotImplementedError(Method not implemented!) def add_AgentServicer_to_server(servicer, server): rpc_method_handlers { VisionProbe: grpc.unary_unary_rpc_method_handler( servicer.VisionProbe, request_deserializeragent__pb2.VisionProbeRequest.FromString, response_serializeragent__pb2.VisionProbeResponse.SerializeToString, ), } generic_handler grpc.method_handlers_generic_handler( agent.Agent, rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) server.add_registered_method_handlers(agent.Agent, rpc_method_handlers)apis/grpc/agent_server.pygRPC 服务端apis/grpc/agent_server.pyclass AgentServicer(agent_pb2_grpc.AgentServicer): async def VisionProbe(self, request, context): md dict(context.invocation_metadata()) if not secrets.compare_digest(md.get(x-internal-token,), settings.AGENT_INTERNAL_TOKEN): await context.abort(grpc.StatusCode.UNAUTHENTICATED, internal token 无效) try: claims verify_id_token(md.get(x-id-token, )) # B 方案身份载体: Java 每请求签发的 HMAC 短寿令牌 (§3.2 红线) except IdTokenError: # 缺失/验签不过/过期(5分钟)拒; 引擎不查库不开户 await context.abort(grpc.StatusCode.UNAUTHENTICATED, x-id-token 缺失/验签不过/已过期) if not request.file or len(request.file) 10*1024*1024: await context.abort(grpc.StatusCode.INVALID_ARGUMENT, f图片缺失或超限: {len(request.file)} bytes) try: out await run_probe(request.file, request.file_name, request.message, ...) except Exception as e: await context.abort(grpc.StatusCode.UNAVAILABLE, f千帆调用失败: {type(e).__name__}: {e}) if context.cancelled(): # 客户端已断: 不应答——Java 拿不到 usage, 自然不记账 (成本诚实账见 §3.3) return logger.info([agent] vision-probe(grpc) user%s trace%s %sms, claims[user_id], claims.get(trace_id, -), out[elapsed_ms]) # 仅日志归属, 不落 stat return agent_pb2.VisionProbeResponse(modelout[model], elapsed_msout[elapsed_ms], contentout[content], usage_to_pb(out[usage]))加字段 / 加方法 → 先改 agent.proto重新生成 grpc_gen再改 agent_server.pyJava 服务侧pom 三个依赖grpc-netty-shaded / grpc-stub / grpc-protobuf全钉 1.51.0!--gRPC 客户端 (Java→Python 引擎内网通道, GRPC_DEV_PLAN §8; 全对齐 1.51 系避 milvus 撞版; netty-shaded 自带遮蔽 netty)-- dependency groupIdio.grpc/groupId artifactIdgrpc-netty-shaded/artifactId version1.51.0/version /dependency dependency groupIdio.grpc/groupId artifactIdgrpc-stub/artifactId version1.51.0/version /dependency dependency groupIdio.grpc/groupId artifactIdgrpc-protobuf/artifactId version1.51.0/version /dependencyprotobuf-maven-plugin 0.6.1protoc 3.21.7 os-maven-plugin 识别平台翻译工具Maven 构建时把接口说明单自动翻成 Java 代码build extensions !-- protobuf-maven-plugin 用 ${os.detected.classifier} 选 protoc 平台二进制 -- extension groupIdkr.motd.maven/groupId artifactIdos-maven-plugin/artifactId version1.7.1/version /extension /extensions plugins plugin groupIdorg.xolstice.maven.plugins/groupId artifactIdprotobuf-maven-plugin/artifactId version0.6.1/version configuration protocArtifactcom.google.protobuf:protoc:3.21.7:exe:${os.detected.classifier}/protocArtifact pluginIdgrpc-java/pluginId pluginArtifactio.grpc:protoc-gen-grpc-java:1.51.0:exe:${os.detected.classifier}/pluginArtifact /configuration executions execution goals goalcompile/goal goalcompile-custom/goal /goals /execution /executions /plugin pluginsrc/main/proto/agent.protosyntax proto3; package agent; // 10-22 用户定名口径: 文件名/包名均不带数字 // Java 统一入口服务 → Python 引擎 的内网调用通道。方法随需求逐个加, 都放进同一个 service。 service Agent { // 千帆视觉探针: 图prompt 一发一答 (HTTP 对位件 POST /agent/v1/vision-probe) rpc VisionProbe(VisionProbeRequest) returns (VisionProbeResponse); } message VisionProbeRequest { bytes file 1; // 图片原始字节 (≤10MB, 与 HTTP 侧同钳; 空/超限INVALID_ARGUMENT) string file_name 2; // 仅用于按扩展名判 mime, 逻辑逐字沿用 agent_api.py 现件 string message 3; // prompt string report 4; // 同 HTTP 表单体, 不透明键不按值路由 (§5.1 口径, 故保 string 不缩 int32); 本通道引擎不落 stat, 只进日志对齐两通道形制 string key 5; // 可选, 同 HTTP 侧; 本通道仅随日志记录 string type 6; // 可选, 缺省 vision-probe (同 HTTP 侧) string model 7; // 可选, 空settings.LLM_VL_MODEL 兜底 (同 HTTP 侧) } message Usage { int64 input_tokens 1; int64 output_tokens 2; } message VisionProbeResponse { // 与 HTTP 侧 _R() 的 data 四键一一对位, 前端/Java 消费口径统一 string model 1; int64 elapsed_ms 2; string content 3; Usage usage 4; // 千帆返回缺失 usage 时置零值 (proto3 默认), 不报错 }生成类 agent.AgentGrpc / agent.AgentOuterClass:: 方式二只跑生成这一步不编译整个工程:mvn protobuf:compile protobuf:compile-customprotobuf:compile 生成消息类 → AgentOuterClass.javaprotobuf:compile-custom 生成 RPC 底座 → AgentGrpc.java不用自己装 protocpom 里配的 com.google.protobuf:protoc:3.21.7 和 io.grpc:protoc-gen-grpc-java:1.51.0 是 Maven 按操作系统自动下载的os-maven-plugin 负责识别平台选对二进制联网第一次跑会自动拉。整个 Java 里真正把请求发出去的只有一处AgentGrpcClient.java:75-78其余代码都是围着它做前处理。另有冒烟件 AgentGrpcSmokeMain.java:56 独立直连不走 Spring仅手动验证用。AgentProbeController.java:26-36RestController RequestMapping(service/v1/agent) Api(tags 引擎 gRPC 通道 (首件 vision-probe)) CrossOrigin Slf4j public class AgentProbeController { Resource private AgentProbeService probeService; PostMapping(/vision-probe) public RMapString, Object visionProbe(RequestParam(file) MultipartFile file, RequestParam(message) String message, RequestParam(value report, required false) String report, RequestParam(value key, required false) String key, RequestParam(value type, required false) String type, RequestParam(value model, required false) String model, RequestHeader(Authorization) String erpToken, RequestAttribute(tokenUserId) String userId) { return probeService.visionProbe(file, message, report, key, type, model, erpToken, userId); } }AgentProbeService.java:43-103Slf4j Service public class AgentProbeService { private static final int MAX_IMAGE_BYTES 10 * 1024 * 1024; Autowired private AgentGrpcClient grpcClient; Autowired private PayUtils payUtils; Autowired private StatCdKeyUseridTokenNumService statService; public RMapString, Object visionProbe(MultipartFile file, String message, String report, String key, String type, String model, String erpToken, String userId) { if (file null || file.isEmpty()) { return R.failure(400, 图片缺失); } byte[] img; try { img file.getBytes(); } catch (Exception e) { return R.failure(400, 图片读取失败); } if (img.length MAX_IMAGE_BYTES) { return R.failure(400, 图片超 10MB 上限); } ERPToken erp; try { erp JSONUtil.toBean(ERPDESUtils.desDecode(erpToken.substring(46), ZB2023), ERPToken.class); } catch (Exception e) { log.warn(vision-probe erpToken 解析失败: {}, e.toString()); return R.failure(401, 凭证无效); } String cdkey erp.getK(); String erpUserId String.valueOf(erp.getS()); try { if (payUtils.isOverdue(erpToken)) { return R.failure(402, 已欠费, 服务不可用); } } catch (Exception e) { log.error(vision-probe 欠费查询失败 cdkey{}: {}, cdkey, e.toString()); return R.failure(402, 余额/欠费状态查询失败); } String dataType type null || type.isEmpty() ? vision-probe : type; String traceId java- userId - System.currentTimeMillis(); long t0 System.currentTimeMillis(); AgentOuterClass.VisionProbeResponse resp; try { resp grpcClient.visionProbe(img, file.getOriginalFilename(), message, report, key, dataType, model, userId, cdkey, erpUserId, traceId); } catch (StatusRuntimeException e) { return mapGrpcError(e, userId, traceId); } recordStat(cdkey, erpUserId, resp, dataType, traceId); log.info([agent] vision-probe(java→grpc) user{} trace{} model{} {}ms 前端耗时{}ms → {}, userId, traceId, resp.getModel(), resp.getElapsedMs(), System.currentTimeMillis() - t0, trim(resp.getContent())); MapString, Object usage new LinkedHashMap(); usage.put(input_tokens, resp.getUsage().getInputTokens()); usage.put(output_tokens, resp.getUsage().getOutputTokens()); MapString, Object data new LinkedHashMap(); data.put(model, resp.getModel()); data.put(elapsed_ms, resp.getElapsedMs()); data.put(content, resp.getContent()); data.put(usage, usage); return R.success(data); }AgentGrpcClient.java:75-78return AgentGrpc.newBlockingStub(channel()) .withInterceptors(MetadataUtils.newAttachHeadersInterceptor(md)) .withDeadlineAfter(deadlineMs, TimeUnit.MILLISECONDS) .visionProbe(req);Slf4j Component public class AgentGrpcClient { private static final int MAX_IMAGE_BYTES 10 * 1024 * 1024; private static final Metadata.KeyString K_INTERNAL_TOKEN Metadata.Key.of(x-internal-token, Metadata.ASCII_STRING_MARSHALLER); private static final Metadata.KeyString K_ID_TOKEN Metadata.Key.of(x-id-token, Metadata.ASCII_STRING_MARSHALLER); Value(${app.agent.grpc.host}) private String host; Value(${app.agent.grpc.port:50051}) private int port; Value(${app.agent.grpc.internal-token:}) private String internalToken; Value(${app.agent.grpc.id-token-secret:}) private String idTokenSecret; Value(${app.agent.grpc.deadline-ms:120000}) private long deadlineMs; private volatile ManagedChannel channel; public AgentOuterClass.VisionProbeResponse visionProbe(byte[] file, String fileName, String message, String report, String key, String type, String model, String userId, String cdkey, String erpUserId, String traceId) { if (file null || file.length 0) { throw new IllegalArgumentException(图片字节缺失); } if (file.length MAX_IMAGE_BYTES) { throw new IllegalArgumentException(图片超 10MB 上限: file.length bytes); } if (internalToken null || internalToken.isEmpty()) { throw new IllegalStateException(app.agent.grpc.internal-token 未配置); } String idToken AgentIdTokenUtil.issue(idTokenSecret, userId, cdkey, erpUserId, traceId, AgentIdTokenUtil.DEFAULT_TTL_SEC); Metadata md new Metadata(); md.put(K_INTERNAL_TOKEN, internalToken); md.put(K_ID_TOKEN, idToken); AgentOuterClass.VisionProbeRequest req AgentOuterClass.VisionProbeRequest.newBuilder() .setFile(ByteString.copyFrom(file)) .setFileName(nvl(fileName)) .setMessage(nvl(message)) .setReport(nvl(report)) .setKey(key null || key.isEmpty() ? 0 : key) .setType(nvl(type)) .setModel(nvl(model)) .build(); return AgentGrpc.newBlockingStub(channel()) .withInterceptors(MetadataUtils.newAttachHeadersInterceptor(md)) .withDeadlineAfter(deadlineMs, TimeUnit.MILLISECONDS) .visionProbe(req); } private ManagedChannel channel() { ManagedChannel ch channel; if (ch null) { synchronized (this) { if (channel null) { log.info(gRPC 通道建立 {}:{} (明文/12MB 上限/deadline {}ms), host, port, deadlineMs); channel ManagedChannelBuilder.forAddress(host, port) .usePlaintext() .maxInboundMessageSize(12 * 1024 * 1024) .build(); } ch channel; } } return ch; } PreDestroy public void shutdown() { if (channel ! null) { channel.shutdown(); try { if (!channel.awaitTermination(5, TimeUnit.SECONDS)) { channel.shutdownNow(); } } catch (InterruptedException e) { channel.shutdownNow(); Thread.currentThread().interrupt(); } } } private static String nvl(String s) { return s null ? : s; } }

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑