gRPC C++ 消息压缩实战指南:从 Channel 级默认压缩到单次 RPC 的压缩覆盖
2026/9/10 14:16:19 网站建设 项目流程

gRPC C++ 消息压缩实战指南:从 Channel 级默认压缩到单次 RPC 的压缩覆盖

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

消息压缩是 gRPC 在网络上节省带宽的核心手段之一。本文以 gRPC C++ 仓库中的 compression 示例为主线,带你完整掌握如何在客户端与服务端分别配置默认压缩算法、如何通过 ClientContext / ServerContext 针对单次 RPC 覆盖压缩配置、如何理解压缩算法枚举与 channel 参数背后的底层映射,以及压缩协商在 HTTP/2 线缆协议层面的错误语义。读完本文,你将能够在自己的 gRPC C++ 服务中自如落地消息压缩配置,并读懂与压缩相关的错误码与 header 行为。

前置知识:从 Hello World 起步

本文构建在 Hello World 示例 的基础上,默认你已经跑通过examples/cpp/helloworld,理解 gRPC 的基本工作方式(proto 定义、代码生成、Stub 与 Server 的搭建)。压缩示例复用了同一个 helloworld.proto,其中定义了Greeter服务与SayHello一元 RPC:

syntax = "proto3"; package helloworld; service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {} // ... 其他 streaming 方法 } message HelloRequest { string name = 1; } message HelloReply { string message = 1; }

在仓库中该示例位于examples/cpp/compression目录,包含greeter_client.ccgreeter_server.cc两个源文件,以及 Makefile、CMakeLists.txt、BUILD(Bazel)三套构建入口。查看完整示例代码请参考 greeter_client.cc 与 greeter_server.cc。

获取示例源码与进入目录

本文属于 gRPCexamples目录下的 C++ 示例。若你尚未拉取仓库,可克隆 gRPC 官方仓库并切换到最新的稳定发布 tag(RELEASE_TAG_HERE请替换为真实 tag,如v1.6x.x):

$ git clone -b RELEASE_TAG_HERE https://github.com/grpc/grpc

进入压缩示例所在目录:

$ cd examples/cpp/compression/

需要说明的是,本文依赖的 helloworld.proto 位于examples/protos目录,示例代码通过相对路径../../protos/引用它。

生成 gRPC 代码

方式一:Makefile 目标

示例自带的 Makefile 通过模式规则把.proto生成.pb.cc.grpc.pb.cc。直接运行:

$ make helloworld.grpc.pb.cc helloworld.pb.cc

该命令内部等价于先后调用 protoc 与 grpc_cpp_plugin 完成两次生成——一次产出 gRPC 服务桩代码(--grpc_out),一次产出 protobuf 消息代码(--cpp_out):

$ protoc -I ../../protos/ --grpc_out=. --plugin=protoc-gen-grpc=grpc_cpp_plugin ../../protos/helloworld.proto $ protoc -I ../../protos/ --cpp_out=. ../../protos/helloworld.proto

两条命令均以examples/protos-I头文件搜索路径,最终在当前目录生成:

  • helloworld.pb.h/helloworld.pb.ccHelloRequestHelloReply等消息类;
  • helloworld.grpc.pb.h/helloworld.grpc.pb.ccGreeter::Stub(客户端)与Greeter::Service(服务端基类)。

方式二:CMake 与 Bazel

  • 基于 CMake 时,CMakeLists.txt 通过add_custom_command直接调用_PROTOBUF_PROTOCgrpc_cpp_plugin--plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}"),并把生成源文件编入hw_grpc_proto静态库,随后构建greeter_clientgreeter_server两个可执行目标。
  • 基于 Bazel 时,BUILD 中定义compression_clientcompression_server两个cc_binary,依赖//:grpc++//examples/protos:helloworld_cc_grpc,并通过defines = ["BAZEL_BUILD"]让源码走#ifdef BAZEL_BUILD分支、按examples/protos/helloworld.grpc.pb.h的路径去 include 生成头文件。

编写客户端:Channel 级默认压缩 + 单次 RPC 覆盖

压缩示例的客户端与 Hello World 客户端结构一致,区别在于引入了两层压缩配置。完整代码见 greeter_client.cc。

第 1 层:通过 ChannelArguments 设置 Channel 默认压缩算法

在创建 Channel 之前,先构造grpc::ChannelArguments并调用SetCompressionAlgorithm,将其作为默认压缩算法传入grpc::CreateCustomChannel

ChannelArguments args; // Set the default compression algorithm for the channel. args.SetCompressionAlgorithm(GRPC_COMPRESS_GZIP); GreeterClient greeter(grpc::CreateCustomChannel( "localhost:50051", grpc::InsecureChannelCredentials(), args));

这段配置的语义是:凡是没有显式指定压缩算法的 RPC,都按 GZIP 进行压缩。可以从源码印证其底层机制——channel_arguments.cc 中该方法仅是把枚举值映射为 channel 整数参数grpc.default_compression_algorithm

void ChannelArguments::SetCompressionAlgorithm( grpc_compression_algorithm algorithm) { SetInt(GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM, algorithm); }

该参数名常量定义在 compression_types.h,属于 C 层 channel 参数体系(GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM)。除了默认算法,头文件中还声明了两个相关的 channel 参数:

  • grpc.default_compression_levelGRPC_COMPRESSION_CHANNEL_DEFAULT_LEVEL):默认压缩级别,未设置时为GRPC_COMPRESS_LEVEL_NONE
  • grpc.compression_enabled_algorithms_bitsetGRPC_COMPRESSION_CHANNEL_ENABLED_ALGORITHMS_BITSET):以位图声明 Channel 支持的算法集合,未设置位即禁用对应算法;默认全部支持,且不允许禁用GRPC_COMPRESS_NONE

第 2 层:通过 ClientContext 覆盖单次调用的压缩算法

每个 RPC 的压缩配置可以在ClientContext上再次覆盖。示例在SayHello中把本次调用的算法从 Channel 默认的 GZIP 改为 DEFLATE:

ClientContext context; // Overwrite the call's compression algorithm to DEFLATE. context.set_compression_algorithm(GRPC_COMPRESS_DEFLATE); Status status = stub_->SayHello(&context, request, &reply);

ClientContext::set_compression_algorithm的公开接口声明于 client_context.h。这样,在同一 Channel 之上,不同 RPC 可以各用各的压缩算法;优先级为「调用级配置 > Channel 默认配置」。

一个便于观察效果的细节

示例客户端发送的用户名是"world world world world"(重复四次),这是为了让待压缩的载荷具备一定冗余度,便于观察压缩效果。

编写服务端:Server 级默认压缩 + 按调用覆盖

服务端逻辑同样建立在 Hello World 服务实现之上,完整代码见 greeter_server.cc。

通过 ServerBuilder 设置服务端默认压缩算法

ServerBuilder上调用SetDefaultCompressionAlgorithm,即设定整个 Server 的默认压缩算法:

ServerBuilder builder; // Set the default compression algorithm for the server. builder.SetDefaultCompressionAlgorithm(GRPC_COMPRESS_GZIP); builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); builder.RegisterService(&service); std::unique_ptr<Server> server(builder.BuildAndStart());

该方法声明于 server_builder.h。同一个头文件中还有一个值得了解的配套接口SetCompressionAlgorithmSupportStatus(server_builder.h),用于控制服务端是否开启对某算法的支持能力判断,展示了「默认算法」与「算法可用性」是两套相互独立的配置维度。

通过 ServerContext 覆盖单次调用的压缩算法

服务端在方法实现内,通过ServerContext覆盖本次响应的压缩算法:

class GreeterServiceImpl final : public Greeter::Service { Status SayHello(ServerContext* context, const HelloRequest* request, HelloReply* reply) override { // Overwrite the call's compression algorithm to DEFLATE. context->set_compression_algorithm(GRPC_COMPRESS_DEFLATE); std::string prefix("Hello "); reply->set_message(prefix + request->name()); return Status::OK; } };

接口声明于 server_context.h(底层由ServerContextBase::set_compression_algorithm提供)。

压缩算法与压缩级别的枚举体系

示例代码中反复出现的GRPC_COMPRESS_GZIPGRPC_COMPRESS_DEFLATE来自 gRPC C 层公共头文件 compression.h,具体枚举定义在 compression_types.h:

typedef enum { GRPC_COMPRESS_NONE = 0, /* 不压缩 */ GRPC_COMPRESS_DEFLATE, /* DEFLATE */ GRPC_COMPRESS_GZIP, /* GZIP */ GRPC_COMPRESS_ALGORITHMS_COUNT } grpc_compression_algorithm;

需要注意:

  • 当前仓库(截至本文所基于的代码版本)只实现了三种取值:NONE(identity,即不压缩)、DEFLATEGZIP;枚举中残留的TODO(ctiller): snappy注释表明 Snappy 属于规划中但尚未落地的算法;
  • 算法在 wire 协议上的字符串名称可在 compression_internal.cc 中看到明确映射:identitydeflategzip,该名称与 HTTP/2 的grpc-encoding元数据一一对应;
  • 与压缩算法配套的还有「压缩级别」枚举(GRPC_COMPRESS_LEVEL_NONE/LOW/MED/HIGH),见 compression_types.h。级别是对算法的抽象,由实现方根据对端能力自动把级别映射为具体算法与参数(例如 low 映射为 gzip -3、high 映射为 gzip -9)。

压缩在协议层的协商与错误语义

如果只停留在 API 用法上,你可能无法解释「为什么对端不支持的算法会导致特定错误码」。gRPC 压缩的完整行为规范记录在 doc/compression.md,摘要如下。

压缩发生在单条消息粒度

gRPC 的压缩作用在单条 message 粒度(message 的定义见线缆格式文档 PROTOCOL-HTTP2.md),由消息头中的 Compressed-Flag 与grpc-encoding元数据描述。也正因如此,示例中的set_compression_algorithm是针对每一次具体 RPC 设置的。

配置时机与方式

规范允许两种配置时机:

  1. Channel 创建时指定默认压缩,无 per-RPC 配置时生效;
  2. 响应/发送时通过上下文覆盖:
    • 一元 RPC 用{Client,Server}Context设置算法;
    • 流式 RPC 用{Client,Server}Writer,且此时只能选择「禁用压缩」。

对端能力协商与错误语义

通信双方可以「不对称压缩」,即响应方可以选择与请求不同的压缩算法,甚至完全不压缩。协议层由此定义了几条关键规则:

  • 客户端发来的消息若使用了服务端不支持的算法,服务端返回状态UNIMPLEMENTED,并在响应头grpc-accept-encoding中声明其支持的算法列表;
  • 服务端发出的数据若使用了客户端不支持的算法,客户端侧表现为INTERNAL错误;
  • 对端可以选择不主动披露自己支持的全部编码;但一旦收到了用未披露算法压缩的消息,就必须在响应的grpc-accept-encoding头中补上该编码;
  • 服务端若获知客户端不支持某算法(依据客户端最近一次发来的grpc-accept-encoding头),则应直接以未压缩形式发送消息。

显式禁用压缩的意义

规范明确指出:显式禁用压缩后,下一条消息必须原样发送,这一机制用于防范 BEAST/CRIME 类压缩侧信道攻击,一元与流式场景均适用。因此「不压缩」本身也是GRPC_COMPRESS_NONE作为一个正式枚举值存在的原因。

deflate 的精确含义

规范特别强调:gRPC 语境下的deflate指的是zlib 容器结构(RFC 1950)+ deflate 算法(RFC 1951),服务端与客户端都不得发送裸 deflate 数据。这也是实现中把deflate名称绑定到 zlib 结构而非裸 deflate 流的依据。

构建与运行

回到示例目录,先执行完整构建(同时生成代码与两个可执行文件):

$ make

make默认目标为system-check greeter_client greeter_server(见 Makefile)。system-check会校验环境中的protoc版本(要求 3.0.0 或更新)以及grpc_cpp_plugin是否在 PATH 中可用。

先在一个终端启动服务端:

$ ./greeter_server

服务端监听0.0.0.0:50051,并打印:

Server listening on 0.0.0.0:50051

再在另一个终端运行客户端:

$ ./greeter_client

客户端向localhost:50051发起一次SayHello,请求载荷在 Channel 默认 GZIP 下创建连接、又在本次调用中被覆盖为 DEFLATE 压缩发送,收到响应后打印:

Greeter received: Hello world world world world

常见问题与排查要点

现象 1:改动压缩算法后抓包看不出差别。先确认载荷是否足够大或足够冗余——极小的消息压缩后可能更大或基本不变。示例中客户端特意使用"world world world world"作为输入,正是为了制造可压缩的冗余。

现象 2:调用返回UNIMPLEMENTED说明本次请求使用的压缩算法不在服务端支持集内。请核对两端grpc_compression_algorithm的取值是否一致,并观察服务端响应的grpc-accept-encoding头确认其支持列表。

现象 3:调用返回INTERNAL往往是服务端发出的数据用了客户端不支持的编码。注意 gRPC 的压缩能力协商以grpc-accept-encoding元数据为载体,跨版本部署时需保证两端 gRPC 版本与编译开关一致。

现象 4:默认压缩没生效。请确认没有在ClientContext/ServerContext/ Writer 层面做更高优先级的覆盖,同时确认没有在 channel 参数中通过GRPC_COMPRESSION_CHANNEL_ENABLED_ALGORITHMS_BITSET禁用该算法——算法被禁用时即使设置了默认值也不会启用。

小结

通过 compression 示例 与源码交叉验证,可以归纳出 gRPC C++ 压缩配置的完整心智模型:

层面客户端 API服务端 API作用域与优先级
默认配置ChannelArguments::SetCompressionAlgorithmServerBuilder::SetDefaultCompressionAlgorithm作用于整个 Channel / Server,优先级最低
调用级覆盖ClientContext::set_compression_algorithmServerContext::set_compression_algorithm作用于单次 RPC,覆盖默认值
能力开关GRPC_COMPRESSION_CHANNEL_ENABLED_ALGORITHMS_BITSET等 channel 参数ServerBuilder::SetCompressionAlgorithmSupportStatus控制可用算法集,独立于默认算法

底层实现上,客户端默认算法最终被写入 channel 参数grpc.default_compression_algorithm(见 channel_arguments.cc),算法枚举与线缆名称(identity/deflate/gzip)的映射集中于 compression_internal.cc,完整的协议语义与安全注意事项则以 doc/compression.md 为准。想进一步验证行为,可继续阅读 helloworld 示例 熟悉基础流程,再回到本文尝试把示例中的 GZIP/DEFLATE 组合替换为GRPC_COMPRESS_NONEGRPC_COMPRESS_GZIP,观察错误码与压缩效果的变化。

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

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

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

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

立即咨询