ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

【C++三方组件】gRPC:现代微服务 RPC

【C++三方组件】gRPC:现代微服务 RPC 【C三方组件】gRPC现代微服务 RPC【摘要】gRPC 用接口定义和代码生成组织跨进程调用提供一元调用、流式传输、状态码、deadline 与取消等机制。本文说明它替应用承担了哪些 RPC 基础工作再通过一个完整 Echo 服务演示从.proto、构建到四种调用模式的使用过程。【版本基准】gRPC 1.82.0Apache-2.0protobuf 35.1BSD-3-ClauseC17。完整的 协议定义、服务端、客户端和构建配置均随文提供。1. WhatgRPC 是什么gRPC 是一个跨语言 RPC 框架。典型使用方式是在.proto中定义消息和服务通过 protoc 及 gRPC 插件生成消息类、客户端代理和服务端接口再由运行时处理调用的传输与生命周期。在常见的 protobuf 使用方式下可以把它分成三层.proto消息结构、服务、方法和流方向 ↓ 代码生成 客户端 Stub ←→ gRPC 运行时与 HTTP/2 ←→ 服务端 Service 实现RPC 保留了“调用一个方法、取得结果”的使用体验但远程调用与本地函数有不同的失败条件网络会断、服务可能不可用、客户端可能已超时而业务仍在执行。gRPC 提供表达这些状态的接口不会让分布式失败自动消失。2. Why为什么要用 RPC 框架用 TCP 加 protobuf 可以实现最小请求应答用 HTTP 加 JSON 也可以建设服务接口。随着服务、语言和调用数量增加基础工作会逐渐超出“把字节发过去”需求自行实现或维护的部分gRPC 提供的机制客户端调用远程方法方法标识、消息编解码、请求与结果关联服务定义、Stub、生成代码多语言共同使用接口各语言消息模型、客户端与接口约定IDL 与多语言生成器复用连接处理多个请求多路复用、流管理、连接状态Channel 与 HTTP/2 传输一次调用返回多条消息消息边界、流方向与结束状态四种 RPC 方法形态限制调用等待时间截止时间、错误分类、取消通知deadline、Status、取消 API传递认证或追踪信息约定附加字段及调用上下文metadata 与扩展接口这些机制让团队能够共享一套调用约定减少客户端和服务端各自维护协议代码的成本。代价是增加代码生成、运行时依赖和接口治理工作调试二进制消息也需要相应工具。如果主要面对浏览器和公开 HTTP APIREST/OpenAPI 可能更便于使用若服务间需要强接口契约、多语言生成代码或流式调用gRPC 值得考虑。是否采用应结合已有平台与团队工具而不是默认所有服务都需要 RPC 框架。3. How从协议定义到可运行服务配套案例提供一个 Echo 服务。服务端和客户端是两个独立可执行文件便于理解实际部署边界本地练习使用回环地址与明文凭据。3.1 定义消息与四种方法echo.proto内容如下syntax proto3; package echo; message Msg { string text 1; } message RepeatRequest { string text 1; int32 count 2; } message Summary { int32 count 1; } service Echo { rpc Shout(Msg) returns (Msg); rpc Repeat(RepeatRequest) returns (stream Msg); rpc Collect(stream Msg) returns (Summary); rpc Chat(stream Msg) returns (stream Msg); rpc Slow(Msg) returns (Msg); }stream出现在参数侧表示客户端发送消息流出现在返回侧表示服务端发送消息流方法形态本例用途可类比的业务Shout一元一条输入、一条输出查询、提交命令Repeat服务端流按次生成多条消息推送进度、日志订阅Collect客户端流接收多条消息后统计上传、批量聚合Chat双向流在同一调用中多轮收发会话、控制通道消息字段号是协议的一部分。删除字段时应保留其编号避免后来赋予另一个含义修改方法的流形态也属于接口变化不能因为消息字段没变就认为旧客户端仍然兼容。3.2 生成代码与 CMake 集成vcpkg 依赖为grpc、protobuf。生成阶段使用 protoc 和grpc_cpp_pluginfind_package(Protobuf CONFIG REQUIRED) find_package(gRPC CONFIG REQUIRED) set(generated_dir ${CMAKE_CURRENT_BINARY_DIR}/generated) file(MAKE_DIRECTORY ${generated_dir}) add_custom_command( OUTPUT ${generated_dir}/echo.pb.cc ${generated_dir}/echo.pb.h ${generated_dir}/echo.grpc.pb.cc ${generated_dir}/echo.grpc.pb.h COMMAND protobuf::protoc --proto_path${CMAKE_CURRENT_SOURCE_DIR} --cpp_out${generated_dir} --grpc_out${generated_dir} --pluginprotoc-gen-grpc$TARGET_FILE:gRPC::grpc_cpp_plugin ${CMAKE_CURRENT_SOURCE_DIR}/echo.proto DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/echo.proto protobuf::protoc gRPC::grpc_cpp_plugin VERBATIM)echo.pb.*是消息代码echo.grpc.pb.*是服务接口和客户端代理。完整 CMake 把它们组成echo_proto目标再由服务端和客户端链接。通过导入目标定位插件可以避免手写 Windows 路径与.exe后缀VERBATIM负责命令参数转义。生成器和运行时应使用兼容版本跨平台交叉编译时还应区分宿主可执行的生成工具与目标平台库。在配套工程中选择BLOG_COMPONENTSgrpc构建步骤见 README。3.3 服务端实现生成的 Service服务端继承echo::Echo::Service重写方法。最简单的一元调用如下grpc::StatusShout(grpc::ServerContext*context,constecho::Msg*request,echo::Msg*reply)override{std::string textrequest-text();std::transform(text.begin(),text.end(),text.begin(),[](unsignedcharc){returnstatic_castchar(std::toupper(c));});reply-set_text(text);returngrpc::Status::OK;}这个转换适用于本例英文输入不是通用 Unicode 大小写转换。业务失败时返回明确的Status例如Repeat的 count 超出 0100 时示例返回INVALID_ARGUMENT。注册服务并启动EchoService service;grpc::ServerBuilder builder;intport0;builder.AddListeningPort(127.0.0.1:18083,grpc::InsecureServerCredentials(),port);builder.RegisterService(service);autoserverbuilder.BuildAndStart();if(!server||port0)return1;server-Wait();Service 对象必须覆盖服务的使用期。这里使用同步服务端接口方法可以在不同工作线程上执行若方法访问共享业务状态需要相应同步。本文没有用同步接口的简洁性来推断它适合任意负载。3.4 一元调用与 metadata客户端首先创建 Channel 和 Stubautochannelgrpc::CreateChannel(127.0.0.1:18083,grpc::InsecureChannelCredentials());autostubecho::Echo::NewStub(channel);grpc::ClientContext context;context.set_deadline(std::chrono::system_clock::now()std::chrono::seconds(3));context.AddMetadata(x-trace-id,demo-001);echo::Msg request,reply;request.set_text(hello, grpc);autostatusstub-Shout(context,request,reply);if(!status.ok())throwstd::runtime_error(status.error_message());Channel 是逻辑通信通道可能涉及名称解析、负载均衡与多个底层连接不应简单等同于一条永不变化的 TCP 连接。通常可以复用 Channel 和 Stub每次 RPC 使用新的ClientContext。完整服务端读取x-trace-id并在 trailing metadata 中返回。客户端在调用完成后通过GetServerTrailingMetadata读取它。metadata 适合调用级追踪或认证信息实际身份与权限仍需业务验证。3.5 服务端流Read 结束后还要 Finish服务端根据 count 循环writer-Write(message)检查返回值后继续客户端逐条读取grpc::ClientContext context;deadline(context);echo::RepeatRequest request;request.set_text(tick);request.set_count(3);autoreaderstub-Repeat(context,request);echo::Msg message;while(reader-Read(message))std::coutstreammessage.text()\n;check(reader-Finish());这里的deadline和check是配套客户端的辅助函数。Read返回 false 说明没有下一条消息但不能单独证明成功正常结束、取消、错误都可能终止读取最终状态由Finish给出。三次Write产生三条应用消息它们属于同一次流式 RPC不应解释成三个独立 HTTP/2 stream也不应假设一条消息必然对应一个 HTTP/2 DATA 帧。3.6 客户端流WritesDone 表示不再发送grpc::ClientContext context;deadline(context);echo::Summary summary;autowriterstub-Collect(context,summary);for(inti0;i3;i)if(!writer-Write(request))break;writer-WritesDone();check(writer-Finish());std::coutcollectedsummary.count()\n;服务端通过ServerReader读取到输入结束再填写最终响应。客户端在写完后调用WritesDone相当于结束发送方向随后仍需取得 RPC 的最终状态。这个模式适合逐步上传或聚合但还要限制总消息数量和单条大小。配套服务端为演示设置了消息数上限。3.7 双向流协议决定收发节奏配套 Chat 使用简单的“一发一收”协议grpc::ClientContext context;deadline(context);autochatstub-Chat(context);for(inti1;i2;i){request.set_text(chat std::to_string(i));if(!chat-Write(request)||!chat-Read(reply)){context.TryCancel();check(chat-Finish());throwstd::runtime_error(chat ended early);}std::coutchatreply.text()\n;}chat-WritesDone();while(chat-Read(reply)){}check(chat-Finish());双向流允许两端独立组织发送与接收本例的交替节奏是应用协议的选择。实际聊天或持续推送可能要分别驱动读写并遵守相应 API 对并发读写的约定避免两端都等待对方先发送。3.8 deadline 与取消给每次调用时间预算配套客户端给 Slow 设置 100ms deadline服务端的工作分成多轮每轮检查取消for(inti0;i30;i){if(context-IsCancelled())return{grpc::StatusCode::CANCELLED,stopped early};std::this_thread::sleep_for(std::chrono::milliseconds(10));}客户端 deadline 到期后返回DEADLINE_EXCEEDED。服务器是否尽快停止业务工作取决于业务是否检查取消、是否能中断下游操作框架不会强行终止任意 C 函数。如果服务端还要调用另一个服务C 中要显式建立 deadline取消传播关系例如通过ClientContext::FromServerContext创建下游 context并按需要设置传播选项。只在入口设置 deadline并不证明任意新建的下游 ClientContext 都会继承它。官方 deadline 说明3.9 运行完整案例在终端 A 运行服务端在终端 B 运行客户端# 终端 A./build/grpc_server127.0.0.1:18083# 终端 B./build/grpc_client127.0.0.1:18083客户端包含有限时长的连接就绪等待每个 RPC 也设置 deadline。成功运行会验证四种模式、metadata、超时和参数错误并输出unaryHELLO, GRPC tracedemo-001 streamtick #1 streamtick #2 streamtick #3 collected3 chatecho: chat 1 chatecho: chat 2 deadline_code4 invalid_code34. 从示例到项目HTTP/2 的多路复用与流控便于并发 RPC但同时活动的流受协商和资源限制也没有消除底层 TCP 丢包造成的阻塞。部署时还要确认代理或负载均衡是否正确支持 gRPC不能只检查“支持 HTTP”就结束。本例的明文凭据用于本地练习。跨不可信网络应配置适当的 TLS、身份认证和授权长连接保活参数应与服务端策略协调而不是任意缩短间隔。超时重试还需结合幂等性判断因为客户端超时不保证服务端完全没有产生副作用。同步 API 便于入门进一步可以学习回调 API 或 CompletionQueue。选择应由并发模型、阻塞工作量和团队维护成本决定。5. 参考资料gRPC C 基础教程服务定义与四种调用模式。生成代码说明Service、Stub 和方法接口。取消机制、deadline。完整构建与运行入口协议生成、服务端、客户端。
返回列表