在 gRPC C++ 服务端实现自定义负载均衡指标:ORCA(Open Request Cost Aggregation)指标上报实践
2026/9/10 13:53:00 网站建设 项目流程

在 gRPC C++ 服务端实现自定义负载均衡指标:ORCA(Open Request Cost Aggregation)指标上报实践

【免费下载链接】grpcC++ based gRPC (C++, Python, Ruby, Objective-C, PHP, C#)项目地址: https://gitcode.com/GitHub_Trending/gr/grpc

gRPC C++ 提供了一套实验性的「自定义指标(custom metrics)」上报机制,允许后端服务向客户端与自定义负载均衡策略暴露 CPU、内存、QPS 乃至业务自定义的请求成本(request cost)数据。本指南以仓库内的 examples/cpp/orca/README.md 及配套 orca_server.cc 为例,完整讲解ServerMetricRecorderOrcaService、每 RPC 指标录制器(CallMetricRecorder)三类核心 API 的搭建、注册与使用方式。读完本文,你将掌握如何在 gRPC C++ 服务中启用 in-band(逐请求 trailer)与 out-of-band(流式负载报告)两条指标通路,为自定义负载均衡策略或 xDS 控制面提供可消费的后端利用率数据。

这套能力解决什么问题

在微服务架构中,gRPC 客户端通常依靠 P2C(pick two least loaded)等启发式算法选择后端,但更精细的负载均衡策略需要真实反映后端「繁忙程度」的指标,而不仅仅是连接数或队列深度。ORCA(Open Request Cost Aggregation)正是为此设计:服务端把 CPU 利用率、内存利用率、QPS、每请求成本等数据上报给客户端,使负载均衡策略可以基于实测后端负载做出调度决策。

从当前仓库的实现结构看,该机制落在两套互为补充的接口上(均位于grpc::experimental命名空间):

  • in-band 逐请求指标:服务端开启每 RPC 指标录制后,处理中的 RPC 可在响应尾部(trailers)附带当次调用的成本与负载指标,供客户端负载均衡策略在单个 RPC 维度上消费。
  • out-of-band 后端指标:通过一个独立的 ORCA 流式服务周期性推送整机维度(server-wide)的利用率快照,即使当前没有业务 RPC 也能把负载变化告知订阅的客户端。

相关公开头文件与实现均可在仓库中找到:orca_service.h、server_metric_recorder.h、call_metric_recorder.h,其底层服务端实现位于 orca_service.cc。ORCA 报告协议(xds.data.orca.v3.OrcaLoadReport等)对应的 xDS v3 定义可参考 orca_service.proto。

服务端初始化:指标录制器 + ORCA 服务 + 逐请求录制

示例文档给出的服务端最小配置分为三部分:创建全局(server-wide)指标录制器、注册 ORCA 服务、启用每 RPC 指标录制。对应完整示例代码见 orca_server.cc,核心片段如下:

GreeterServiceImpl service; // 1. 创建服务端全局指标录制器(server-wide) auto server_metric_recorder = grpc::experimental::ServerMetricRecorder::Create(); // 2. 构造并注册 ORCA out-of-band 上报服务 grpc::experimental::OrcaService orca_service( server_metric_recorder.get(), grpc::experimental::OrcaService::Options().set_min_report_duration( absl::Seconds(0.1))); builder.RegisterService(&orca_service); // 3. 启用逐 RPC(in-band)指标录制 grpc::ServerBuilder::experimental_type(&builder).EnableCallMetricRecording( nullptr);

1.ServerMetricRecorder::Create()——server-wide 指标的单一事实来源

ServerMetricRecorder是整个上报体系的「度量仓库」,它持有后端整体维度(而非单次调用维度)的指标快照。与直接new不同,它必须通过工厂方法Create()创建:

static std::unique_ptr<ServerMetricRecorder> Create();

它同时被 ORCA 服务(out-of-band 流)和EnableCallMetricRecording(in-band 上报的合并来源)读取。头文件注释明确指出:对象生命周期必须长于它所服务的 gRPC Server,即「调用方持有并确保其存活时间覆盖服务端」(见 server_builder.h 中EnableCallMetricRecording的说明)。因此示例代码把它声明在RunServer的栈上、先于server->Wait()存活,是符合要求的用法。

2.OrcaService——对外提供 out-of-band 负载报告

OrcaService是一个可注册进ServerBuilder的 gRPC 服务实现(继承自Service),用途在头文件中写得很清楚:"RPC service implementation for supplying out-of-band backend utilization metrics to clients"(见 orca_service.h)。构造它时必须传入一个ServerMetricRecorder指针:

OrcaService(ServerMetricRecorder* const server_metric_recorder, Options options);

Options结构体只有一个配置项min_report_duration,语义如下:

配置项默认值说明
min_report_durationabsl::Seconds(30)最小上报间隔。客户端请求的间隔低于该值时,服务端将用此值兜底(即客户端可以要求更频繁的上报,但不能突破服务端设定的下限)

示例文档将其下调到absl::Seconds(0.1)(100ms),以便负载均衡器更快感知指标变化。Options使用链式 setter 风格,可通过set_min_report_duration(absl::Duration)修改后整体传给构造函数(见 orca_service.h)。从 orca_service.h 的私有成员还可以看出它的实现方式:服务端内部持有互斥锁,并缓存「最后一次序列化后的响应」及其更新序号(response_slice_response_slice_seq_),只有在指标更新序号变化时才重新生成响应负载,从而避免在两次上报之间无谓地重复序列化。

3.EnableCallMetricRecording——打通 in-band 逐 RPC 指标

ServerBuilder::experimental_type返回一个实验视图(server_builder.h),通过它调用EnableCallMetricRecording即可开启逐调用负载上报:

void EnableCallMetricRecording( experimental::ServerMetricRecorder* server_metric_recorder = nullptr);

其参数说明(server_builder.h)揭示了两个关键事实:

  • 开启后,服务端会在每个 RPC 结束后自动附带负载指标;
  • 调用方可通过ServerContext::ExperimentalGetCallMetricRecorder()为当前调用录制指标;当同时传入可选的server_metric_recorder时,服务端会把全局指标与单调用指标合并上报,其中call metric recorder(逐调用指标)优先级更高

因此文档中传nullptr与示例源码中传server_metric_recorder.get()(见 orca_server.cc)都是合法的:前者仅上报逐调用指标,后者还会把 server-wide 指标合并进每次 RPC 的 trailer 中,实现更完整。

每请求指标:从请求上下文取录制器并上报

开启EnableCallMetricRecording之后,业务服务实现里就能通过请求的ServerContext获取本次调用的指标录制器CallMetricRecorder。若未开启该功能,获取到的将是指针nullptr。示例文档给出了标准的防御式写法:

auto recorder = context->ExperimentalGetCallMetricRecorder(); if (recorder == nullptr) { return Status(grpc::StatusCode::INTERNAL, "Unable to access metrics recorder. Make sure " "EnableCallMetricRecording had been called."); } recorder->RecordCpuUtilizationMetric(0.5);

在 orca_server.cc 的GreeterServiceImpl::SayHello(基于 callback API 的 Unary 服务)中,实际还把这次调用「模拟消耗的数据库查询次数」作为自定义成本指标一并上报:

auto recorder = context->ExperimentalGetCallMetricRecorder(); if (recorder == nullptr) { reactor->Finish({grpc::StatusCode::INTERNAL, "Unable to access metrics recorder. Make sure " "EnableCallMetricRecording had been called."}); return reactor; } recorder->RecordRequestCostMetric("db_queries", 10); recorder->RecordCpuUtilizationMetric(0.5);

获取录制器的接口定义在 server_context.h(experimental::CallMetricRecorder* ExperimentalGetCallMetricRecorder())。需要说明的是:这里用返回值是否为nullptr来兜底「未开启录制」的误用场景,把错误显式地转成INTERNAL状态返回,是一种值得借鉴的健壮性处理。

CallMetricRecorder 支持的全部指标类型

接口的完整定义见 call_metric_recorder.h。除 CPU 利用率外,还提供内存、应用自定义利用率、QPS、EPS、命名指标等录制方法。全部方法均返回CallMetricRecorder&以支持链式调用,且对同名指标多次调用会覆盖先前存储的值。各指标的含义与取值范围如下:

方法指标含义合法范围(超出即被忽略)
RecordCpuUtilizationMetric(double)CPU 利用率[0, +∞),可大于 1.0(表示超出软上限)
RecordMemoryUtilizationMetric(double)内存利用率[0, 1]
RecordApplicationUtilizationMetric(double)应用自定义利用率[0, +∞),可大于 1.0
RecordQpsMetric(double)每秒查询数(QPS)[0, +∞)
RecordEpsMetric(double)每秒错误数(EPS)[0, +∞)
RecordUtilizationMetric(name, double)具名资源利用率[0, 1]
RecordRequestCostMetric(name, double)具名请求成本(如 DB 查询数)—(随协议语义,超范围值被忽略)
RecordNamedMetric(name, double)应用自定义不透明指标

两个具名指标的注意事项也写在了接口注释中(call_metric_recorder.h):指标名string_ref的生命周期必须比 RPC 本身更长——因为它们会随 RPC 结束作为 trailers 发送,建议使用全局常量字符串(例如示例中的"db_queries"这类贯穿进程生命周期的字面量)。

Out-of-band 指标:直接向 server-wide 录制器写入

不需要与某个 RPC 绑定、希望周期性反映后端整体状态的指标(如后台任务导致的 CPU 抬升),可以直接调用ServerMetricRecorderSet*系列方法写入,无需经过请求上下文:

server_metric_recorder->SetCpuUtilization(0.75);

任何持有该ServerMetricRecorder的线程都可以在任意时刻调用,随后订阅了 ORCA 服务的客户端(以及启用了合并上报的逐 RPC trailer)都会在下一次上报中拿到新值。

ServerMetricRecorder提供的写入与清理接口(完整定义见 server_metric_recorder.h):

分类写入方法清理方法范围约束
CPUSetCpuUtilizationClearCpuUtilization[0, +∞),可大于 1.0
内存SetMemoryUtilizationClearMemoryUtilization[0, 1]
应用利用率SetApplicationUtilizationClearApplicationUtilization[0, +∞)
QPSSetQpsClearQps[0, +∞)
EPSSetEpsClearEps[0, +∞)
具名利用率SetNamedUtilization/SetAllNamedUtilizationClearNamedUtilization具名值[0, 1]SetAllNamedUtilization整体替换不做范围校验

与逐调用录制器一致的语义在此同样成立:有效范围内再次Set会覆盖旧值,超出范围的值会被拒绝。此外所有Set*方法都会使内部指标状态进入新「序号」——从 orca_service.h 及 server_metric_recorder.h 的声明可以推断,上报端依赖「序号变化」判断指标是否更新,从而决定是否值得重新序列化与推送,避免高频空转。

把示例跑起来:完整服务端剖析

与多数「可读性优先」的示例不同,仓库中的 orca_server.cc 是一个可直接构建运行的完整程序,把上文三部分组件组装进了标准 gRPC 服务:

  1. 命令行解析基于 Abseil flags,默认监听端口50051ABSL_FLAG(uint16_t, port, 50051, ...),orca_server.cc);
  2. 服务采用 callback API 的 Unary 服务GreeterServiceImpl(继承Greeter::CallbackService),业务逻辑即前面展示的SayHello
  3. RunServer内依次创建ServerMetricRecorderOrcaService(上报间隔 100ms)、注册 ORCA 服务、开启逐调用录制,并把server_metric_recorder一并传给EnableCallMetricRecording(orca_server.cc),使两条通路都能消费全局指标;
  4. 额外开启 gRPC 默认健康检查服务grpc::EnableDefaultHealthCheckService(true)(orca_server.cc),便于基础设施探活。

构建与运行(Bazel)

本示例目录提供 BUILD 文件,其orca_server目标声明了对//:grpcpp_orca_service//:grpc++的依赖——这也是你在自己的 Bazel 工程中接入 ORCA 能力时必须额外链接的目标(纯grpc++不包含 ORCA 扩展):

# 构建 bazel build //examples/cpp/orca:orca_server # 运行(默认监听 0.0.0.0:50051) bazel run //examples/cpp/orca:orca_server # 自定义端口 bazel run //examples/cpp/orca:orca_server -- --port=50052

源码里通过#include <grpcpp/ext/orca_service.h>#include <grpcpp/ext/call_metric_recorder.h>#include <grpcpp/ext/server_metric_recorder.h>引入相关 API(orca_server.cc),与 C++ Quick Start 中常规 gRPC 程序的编译方式(对应仓库根目录的 BUILDING.md 与 CMake 构建体系)一致,只是需要把上述扩展库一并链接。

谁来消费这些指标

需要注意,普通的helloworld客户端看到的行为与常规示例完全相同(SayHello正常返回问候语),只有实现了 ORCA 消费能力的客户端/负载均衡策略才会主动订阅 out-of-band 报告或在 trailer 中解析 in-band 指标。换言之,本示例服务端本身只负责「生产」指标:其价值需要配合具备自定义负载均衡(如基于 Open Request Cost Aggregation 的加权调度)的客户端才能真正释放,这也是 xDS 场景下 gRPC 客户端侧负载均衡(client-side load balancing)数据链路的服务端半边。

常见错误与排查要点

结合接口注释与示例代码,接入时最容易踩的坑集中在四处:

  1. 忘掉EnableCallMetricRecording:此时ExperimentalGetCallMetricRecorder()返回nullptr,逐调用上报静默失效。示例代码的nullptr检查 +INTERNAL错误是标准做法。
  2. 生命周期错误ServerMetricRecorder必须在服务端构建前创建,并存活到服务端停止之后。若在BuildAndStart()之后或局部作用域提前销毁,out-of-band 上报与 in-band 合并都会读取到悬垂状态。可在进程/线程中更长的作用域持有它(如类的成员或RunServer栈上),而不要在 RPC 回调内部临时创建。
  3. 超出取值范围的数据被丢弃:CPU 可大于 1.0(代表超出软上限),但内存与具名利用率严格限定在[0, 1];写入越界值不会被报错,而是静默拒绝。若发现指标「没变化」,先核对取值是否越界。
  4. 客户端请求上报间隔低于服务端下限OrcaService::Options().set_min_report_duration(...)是服务端侧的兜底下限。示例设置为0.1s属高频演示配置;生产环境中未显式设置时默认是30s(orca_service.h),需要按负载均衡策略的实际敏感度权衡带宽与实时性。

小结

基于 examples/cpp/orca 示例,gRPC C++ 服务端的自定义指标上报只需要三步:用ServerMetricRecorder::Create()建立全局指标仓库,用OrcaService+Options向客户端开放 out-of-band 流式报告,再用EnableCallMetricRecording打开 in-band 逐 RPC 通路。三条写入路径——请求上下文内的CallMetricRecorder、直接调用ServerMetricRecorderSet*、以及二者在 trailer 中的合并——共同构成了自定义负载均衡策略所依赖的完整后端负载视图。结合 orca_service.h、server_metric_recorder.h 与 call_metric_recorder.h 三份头文件,以及 orca_service.cc 的服务端实现,你即可在自己项目的负载均衡链路中复刻这套指标通路。

【免费下载链接】grpcC++ based gRPC (C++, Python, Ruby, Objective-C, PHP, C#)项目地址: https://gitcode.com/GitHub_Trending/gr/grpc

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询