C# 中的 gRPC 压缩

Posted

技术标签:

【中文标题】C# 中的 gRPC 压缩【英文标题】:gRPC compression in C# 【发布时间】:2018-08-08 11:06:39 【问题描述】:

在使用 C# 时是否可以在 gRPC 中控制响应消息压缩?

This question 收到评论但没有回复。评论指向gRPC issue #10902,上面写着:

功能已经存在,但您需要通过设置适当的通道选项或在 RPC 中设置元数据条目来[启用?]。

那里链接的test class 显示了一些可能性:

new WriteOptions(WriteFlags.NoCompress)

但这对我没有影响:无论此设置如何,通过 WAN 进行的呼叫都需要相同的时间。

var compressionMetadata = new Metadata

     new Metadata.Entry(Metadata.CompressionRequestAlgorithmMetadataKey, "gzip") 
;

同样,测试显示更改此设置后通话时间没有变化。 (CompressionRequestAlgorithmMetadataKey常量是内部的,我反编译得到字符串值)。

有一个诱人的 CompressionLevel 枚举 in the C# github repo 但我找不到它的任何用法。

gRPC issue #4170(在 C# 中实现压缩支持)已关闭,但显示

可以用C#完成,但绝对不友好

有没有人能帮我举个例子来说明如何做到这一点?我不介意它是否对用户不友好,我只想能够做到!我正在通过网络发送一些大的(~8Mb)响应,我想对它们进行一些控制。虽然我可以在发送之前压缩数据,但最好让客户端对所使用的压缩方法/级别进行一些控制。

(我会发布一些代码,但我只是使用来自C# sample code 的测试代码和一个大的硬编码字符串)

tl;dr 我不知道如何使用 C# gRPC 控制压缩设置。所有看起来可能的设置都不会影响通过网络发送响应所花费的时间。

编辑 在 Jon Skeet 关于查看有线流量的评论之后,我运行了一个新测试,其中响应是字符串 The quick brown fox jumps over the lazy dog 重复 5000 次。我通过

打开了 gRPC 跟踪
        Environment.SetEnvironmentVariable("GRPC_TRACE", "compression");
        Environment.SetEnvironmentVariable("GRPC_VERBOSITY", "DEBUG");
        GrpcEnvironment.SetLogger(new ConsoleLogger());

... 在客户端和服务器上。我使用 Wireshark 捕获网络流量,在我的 gRPC 服务器端口上解码 HTTP2 流量。

测试#1:没有控制压缩的特定代码

可以看到流量没有被压缩。

测试#2:服务器关闭压缩

在返回响应之前在服务器端设置context.WriteOptions = new WriteOptions(WriteFlags.NoCompress);。 可以看到流量没有被压缩。

测试#3:服务器启用压缩

在返回响应之前在服务器端设置context.ResponseTrailers.Add(new Metadata.Entry("grpc-internal-encoding-request", "gzip"));。 可以看到流量没有被压缩。

测试#4:客户端启用压缩

调用服务器时设置元数据头:var headers = new Metadata new Metadata.Entry("grpc-internal-encoding-request", "gzip"); 客户端 gRPC 跟踪记录以下内容

D0228 16:53:21.566026 0 C:\jenkins\workspace\gRPC_build_artifacts\platform\windows\workspace_csharp_ext_windows_x64\src\core\ext\filters\http\message_compress\message_compress_filter.cc:262:算法“gzip”已启用但已决定不要压缩。输入大小:0

我认为这是试图压缩 request 而不是 response? 可以看到流量没有被压缩。

编辑#2 Jan Tattermusch 做到了。在我的服务器端方法中添加此代码

        var headers = new Metadatanew Metadata.Entry("grpc-internal-encoding-request", "gzip");            
        await context.WriteResponseHeadersAsync(headers);

...成功了! Wireshark 显示响应被压缩并显示 gRPC 日志

压缩 [gzip] 225002 字节与 742 字节(节省 99.67%)

太棒了!可悲的是,这导致了我的下一个问题:压缩的开销导致此方法调用比未压缩的数据! 更多的谷歌搜索让我找到了更好的选择:创建Server 对象时,您可以传递ChannelOptions 的集合。我设置如下:

var options = new[] new ChannelOption("grpc.default_compression_level", (int) CompressionLevel.Low);

这会将压缩设置到更好的级别(对于我在此处发送的数据),并且具有启用 gzip 压缩作为所有调用的默认值的副作用。现在每个方法都必须显式关闭压缩(通过我的测试#2 中的WriteOptions)。

有趣的是,尝试通过 grpc-internal-encoding-request 覆盖默认压缩现在非常慢,我猜是由于引发了异常:

src\core\lib\surface\call.cc:1018: prepare_application_metadata: "created":"@1520008670.738000000","description":"不允许重复的元数据","file":"C:\jenkins\workspace \gRPC_build_artifacts\platform\windows\workspace_csharp_ext_windows_x64\src\core\lib\transport\metadata_batch.cc","file_line":111,"key":"grpc-internal-encoding-request","value":"gzip"

这意味着我不能在每次调用的基础上更改和更改压缩算法或级别,但鉴于仅打开压缩带来的性能优势,我是一个快乐的人。

【问题讨论】:

您是否查看过网络上实际传输的数据?这样做,您应该能够看到 NoCompress 真的 是否没有效果。我的猜测是确实如此,而瓶颈并不在您认为的位置。 我使用 Wireshark 检查了各种场景下的 HTTP2 流量(详情在编辑中)。它表明无论我使用何种设置,流量都没有压缩。 谢谢 - 现在这是一个更有说服力的问题 :) 【参考方案1】:

测试 #1 和测试 #2 的行为似乎符合预期。

测试#3:

AFAIK grpc C# 目前不支持在服务器端启用压缩 您尝试在服务器端使用的标头“grpc-internal-encoding-request”元数据标头只能在客户端使用 您正在将标头添加到 context.ResponseTrailers.Add() - 响应预告片由服务器在所有响应已传输后发送,因此无论如何这都没有机会工作。如果支持设置压缩,则需要将标头添加到 context.ResponseHeaders()。

测试#4:

我相信您所做的(= 设置“grpc-internal-encoding-request”)是正确的,只是您发送的请求大小为 0,因此 grpc 认为没有必要进行压缩(有一个大小阈值,在该阈值下不会启动压缩)。

【讨论】:

太棒了!我没有看到 context.WriteResponseHeadersAsync 方法,所以将元数据添加到了错误的位置。 @MorganPeat 对你有用吗?我也在做同样的事情,一切正常,但我怎么知道压缩正在发生在 localhost 上运行测试?

以上是关于C# 中的 gRPC 压缩的主要内容,如果未能解决你的问题,请参考以下文章

c# 使用grpc代替WCF

grpc 开发进阶 - 使用压缩器 compressor

gRPC应用golang

centos 7下部署grpc

Q新闻丨Facebook开源新的压缩算法,性能超zlib;来自Google的RPC框架:gRPC1.0发布

使用 c# 压缩文件夹中的文件并返回响应