RPC 系列(十一):gRPC 大数据量消息传递

本文探讨 gRPC 中大数据量消息的传输限制及相应的两种解决方法:修改限制值大小和流式数据传输,并给出 gRPC C++ 版本下采用流式数据传输的示例,同时说明如何在 Visual Studio 下进行 proto 文件编写、编译以及 gRPC 项目的配置。

简介

在实际项目的实施过程中讨论服务对接时,谈到了一个问题:gRPC 不支持大数据量的消息传递,并且官方也说如果是涉及大量数据交互的服务,建议采用其他的方案。这是一个致命的问题——之前做方案选型的时候,看到的关于 gRPC 等多种 RPC 服务框架对比的文章中都说 protocol buffer 的序列化能大大减少数据量,提高数据的传输效率,但现在看来似乎不太科学。于是花了些时间对 gRPC 传输大数据量消息的情况做了调查和测试。

gRPC 的默认消息大小限制

gRPC 默认的消息传输大小由 #define GRPC_DEFAULT_MAX_RECV_MESSAGE_LENGTH (4 * 1024 * 1024) 决定,也就是 4MB。

比如传递一个数组,数组的元素如果是 double,因为 double 是 8 字节,所以传递的数据数组长度最大就只能是 1024 * 512。如果大于该大小,则会提示:

Error: grpc: received message larger than max (XXXXX vs. 4194304)

其中 XXXXX 就是你的真实数据大小。这个只是提示,并不会在编译的时候报错,而是客户端在发起请求的时候通过 status 的值提示。

做这样的限制并不是说 gRPC 对大数据量传输不支持,而是开发团队认为这样可以提醒 gRPC 使用者考虑程序中会进行大数据量传输的问题。

方法一:调大限制值

4MB 这个数据量的确太小了,不能满足大多数的 RPC 服务调用情况。第一种方法是在 ServerBuilder 中,通过 SetMaxReceiveMessageSize(int) 设置最大允许字节长度。因为这里的参数为 int 型,所以其最大的字节允许长度也就是 INT_MAX = 2147483647(2GB)。

可能就有朋友看到这里会问了:谷歌的工程师难道就不能将这个参数设置成其他类型吗?比如 unsigned long long 之类的,能提供更大的传输量许可。但是仔细考虑下,这个大小已经很大了:抛开数据在传输中的时间代价,gRPC 接收到消息数据后都是直接在内存中保存数据,也就意味着数据接收到后至少也用掉 2GB 的内存,服务端仅接收数据就使用了 2GB,算是十分消耗资源了。所以 gRPC 的开发团队认为没必要再提供更高的数据传输量阈值,如果实在需要更大量的数据传输,那么就是你的算法需要改进了,或者可以采用流式处理。

于是就有了第二种大数据量传输方案。

方法二:流式传输

在查找资料时,发现关于 gRPC 流式传输的资料很少,采用 gRPC C++ 的更是几乎没看到,所以本文后续的测试采用 gRPC C++ 进行编写,Java 与 Go 版本也有对应的流式处理方案可以参考。

gRPC 流式传输示例

编写 proto 文件(calculater.proto)

该 RPC 服务中通过 stream 关键字来标识传入的数据采用的是流式传输,其他的服务消息定义则和普通的定义方式没有区别。

编译 proto 文件

本示例中使用的编译平台是 Visual Studio,可以在 VS 的【工具】→【扩展和更新】中安装 Protobuf Language Service 扩展,该语言服务能提供对 proto 文件的语法高亮和错误检查,十分方便。

对于已经编写好的 proto 文件,需要定义编译方式以完成服务端和客户端可以使用的 C++ 类文件的生成。首先在【解决方案】列表的 proto 文件上点击右键 →【属性】→【常规】→【项类型】,选择【自定义生成工具】,并点击应用。之后【常规】下面会多出【自定义生成工具】设置,在【自定义生成工具】→【常规】中,命令行写入:

protoc --cpp_out=. calculater.proto
protoc --grpc_out=. --plugin=protoc-gen-grpc=grpc_cpp_plugin.exe calculater.proto

然后在【输出】栏中写入 none——如果此处不写入东西的话,编译的时候会因为没有输出而跳过该文件的编译。之后点击确定即可。

同时将 gRPC 和 protocol buffer 的代码生成工具 protoc.exe 和 grpc_cpp_plugin.exe 两个 exe 拷贝到项目文件夹下(就是 .vcxproj 文件所在的文件夹)。然后编译该项目,或者在解决方案文件列表中的 proto 文件上点击右键选择【编译】,即可完成 proto 文件的编译。编译后生成的代码在项目文件夹下,但不会被自动加载到项目中,需要根据情况手动加载到需要的项目中。

服务端代码

新建一个 Win32 控制台程序,在新建项目的时候,需要完成 gRPC 相关的库依赖配置。服务端代码包括两部分:服务实现代码和服务的启动代码,前者实现 proto 中定义的流式服务接口,后者完成 gRPC 服务器实例的创建与启动。

客户端代码

新建一个 Win32 项目,设置和配置见上文服务端一节。客户端代码包括服务调用代码和客户端启动代码两部分,通过流式接口向服务端发送大数据。

服务端和客户端启动后,服务端即可接收到客户端通过流发送的数据。

总结与讨论

通过本文的测试可以看到,gRPC 是可以进行大数据量消息的传输的。当然这里没有去探讨这个传输的效率——PB 对数据的序列化压缩率很高,如果效率出现问题,那应该是在反序列化阶段,这需要进一步测试才能知道答案。

其实在讨论中,我们也提到了第三个解决方案:采用其他的数据传输方案(如将数据序列化为其他形式并传输)。但是在对 gRPC 和 Protocol Buffer 进一步了解后,这个方案我认为可以否定——特别是以 JSON 来传输的提议,窃以为应该直接否掉:数据量大后,JSON 这种松散方式数据大小更大,且对象过多,在应用的数据处理中操作更耗时。

无论采用哪种数据序列化方式,在没有考虑额外的压缩的情况下,传输过程中其实只是对数据实体的传输,数据原始是什么就是什么——是三个 double 构成的对象就是传三个 double 值(3×8 字节),反序列化的时候再用接收到的值构建一个新的对象。所以不外乎就是「对象 → 序列化 → 供传输的数据 → 反序列化 → 对象」的过程。那么采用其他的数据传输方式就有两个问题了:不见得有 PB 高效,且框架复杂性增加。就目前看来,采用 gRPC 提供的解决方案已经够用了。

系列导航


   转载规则


《RPC 系列(十一):gRPC 大数据量消息传递》 由 吴杭沉 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
RPC 系列(十二):gRPC 客户端负载均衡 RPC 系列(十二):gRPC 客户端负载均衡
本篇将基于 etcd 服务发现,介绍如何实现 gRPC 客户端负载均衡。 gRPC 负载均衡的三种方案gRPC 官方文档提供了关于 gRPC 负载均衡的方案(Load Balancing in gRPC),此方案是为 gRPC 设计的,下面
2023-11-21
下一篇 
RPC 系列(十):gRPC 流式消息的大小限制 RPC 系列(十):gRPC 流式消息的大小限制
在工作中遇到了 gRPC 的 stream 通信(server 通过 stream 向 client 返回一个消息的 stream)中 client 收不到消息的情况。排查发现,收不到的这条消息比较大,跟同事讨论后认为,可能是 gRPC 限
2023-11-07
  目录