1.摘要

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

2.简介

在项目的实施过程中,给导师提出了使用gRPC构建微服务的方案,这方面我们并没有任何经验,也没有有经验的师兄和老师指导,一切都是摸着石头过河。今天在和项目参与人员讨论服务对接的过程中,突然讨论到:gRPC不支持大数据量的消息传递,并且官方也说如果是涉及到大量数据的交互的服务,建议采用其他的方案。这真是一个致命的问题,之前在方案选型的时候还真是没有注意到说大数据量的问题,看到的关于gRPC等多种RPC服务框架对比的帖子中都说protocol buffer的序列化能大大减少数据量,提高数据的传输效率,但现在看来似乎不太科学啊。于是花了些时间对gRPC传输大数据量数据的消息的情况做了调查和测试。

gRPC默认的消息传输大小为#define GRPC_DEFAULT_MAX_RECV_MESSAGE_LENGTH (4 * 1024 * 1024) byte,也就是4M大小。比如:传递一个数组,数组的元素如果是double,因为double是8byte,所以你传递的数据数组长度最大就只能是1024*512,如果大于该大小,则会提示:Error: grpc: received message larger than max (XXXXX vs. 4194304) 这个XXXXX就是你的真实数据大小。这个只是提示,并不会在编译的时候报错,而是客户端在发起请求的时候通过status的值提示。做这样的限制并不是说gRPC对大数据量传输不支持,而是开发团队认为这样可以提醒gRPC使用者考虑程序中会进行大数据量的传输问题。

4M这个数据量的确太小了,不能满足大多数的RPC服务调用情况,那么有什么办法提升这个数据传输量呢?这里有两个方法:1、在ServerBuilder中,通过SetMaxReceiveMessageSize(int)设置这个最大允许字节长度,因为这里的参数为Int型,所以其最大的字节允许长度也就是INT_MAX=2147483647 (2G)。可能就有朋友看到这里就会问了:谷歌的工程师难道就不能将这个参数设置其他类型吗?unsigned long long之类的多好啊,能提供更大的传输量许可。但是仔细考虑下,这个大小已经很大了,抛开数据在传输中的时间代价,考虑gRPC接收到消息数据后gRPC都是直接在内存中保存数据,也就意味着数据接收到后至少也用掉2G的内存,你的服务单接收数据就使用了2G,算是十分消耗资源了,所以gRPC的开发团队认为没必要再提供更高的数据传输量阈值,如果实在需要更大量的数据传输,那么就是你的算法需要改进了,或可以采用流式处理。所以这里就有了第二个大数据量传输方案。2、采用流式传输。在查找资料时,发现关于gRPC流式传输的帖子很少,采用gRPC C++的更是几乎没看到,所以本文后续的测试采用gRPC C++进行编写,java版本的可参考[5],go版本的可以参考[4]。

3.gRPC流式传输示例

3.1编写proto文件(calculater.proto)

syntax = "proto3";
import "CommonData.proto";
package calc;service Caltulator {rpc LargeDataSetStream(stream Line3D) returns (Response){}
}
message Response {int32 sum = 1;
}
message Point3D{double  x=1;double y=2;double z=3;
}
message Line3D{repeated Point3D points=1;
}

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

3.2编译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文件的编译。编译后生成的代码在项目文件夹下,但不会被自动加载到项目中,所以自己根据情况手动加载到需要的项目中。

3.3服务端代码

新建一个win32控制台程序,在新建项目的时候,需要:

  1. 项目设置项中去掉“生命周期检查”和“预编译头”的勾选状态(具体名字我忘了,大致是类似的两个选项)。
  2. 在项目【属性】à【配置属性】à【C/C++】à【预处理器】à【预处理器定义】栏中添加_WIN32_WINNT=0x600定义。
  3. 项目中需要链接的gRPC及其依赖的库包括:grpc.lib、grpc++.lib、gpr.lib、libprotobufd.lib、zlibstaticd.lib、ssl.lib、crypto.lib,同时需要链接winsocket2的库文件ws2_32.lib
  4. 需要引用的头文件主要是gRPC和protocol buffer的头文件。
  5. 加载生成的gRPC代码,重写LargeDataSetStream 函数。

服务实现代码为:

class CalcualtorService:public calc::Caltulator::Service
{
public: virtual ::grpc::Status LargeDataSetStream(::grpc::ServerContext* context, ::grpc::ServerReader<::calc::Line3D>* reader, ::calc::Response* response) {unsigned long long numID=0;system_clock::time_point start_time = system_clock::now();::calc::Line3D oneLine;while (reader->Read(&oneLine)){::google::protobuf::RepeatedPtrField< ::calc::Point3D > points=oneLine.points();for (auto& onePoint : points){std::cout << ++numID << ": " << onePoint.x() << "\t" << onePoint.y() << "\t" << onePoint.z() << std::endl;}}system_clock::time_point end_time = system_clock::now();auto secs = std::chrono::duration_cast<std::chrono::seconds>(end_time - start_time);response->set_sum(secs.count());std::cout <<"耗时(s):"<<secs.count()<< std::endl; return grpc::Status::OK;
};

服务的启动代码为:

int main()
{std::string serverAddr("0.0.0.0:8000");CalcualtorService service;grpc::ServerBuilder builder;//builder.SetMaxReceiveMessageSize(INT_MAX);builder.AddListeningPort(serverAddr, grpc::InsecureServerCredentials());builder.RegisterService(&service);std::unique_ptr<grpc::Server> server(builder.BuildAndStart());std::cout << "Server listening on " << serverAddr << std::endl;server->Wait();return 0;
}

3.4客户端代码

新建一个win32项目,设置和配置见3.3。

客户端服务调用代码:

#include "stdafx.h"
#include <memory>
#include "grpc++/grpc++.h"
#include "calculater.grpc.pb.h"class Client {
public:Client(std::shared_ptr<grpc::Channel> channal):stub_(calc::Caltulator::NewStub(channal)){}void LargeDatasetTest() {std::vector<::calc::Line3D*> dataSet;int numberOfData = 1024;int pointSize = 102400;dataSet.reserve(numberOfData);for (int i = 0; i < numberOfData; i++){::calc::Line3D* oneLine =new ::calc::Line3D;for (int k = 0; k < pointSize; k++){::calc::Point3D* onePoint = oneLine->add_points();onePoint->set_x(1.0);onePoint->set_y(2.0);onePoint->set_z(3.0);}dataSet.push_back(oneLine);}//模拟大体量数据完毕calc::Response response;grpc::ClientContext context;std::unique_ptr< ::grpc::ClientWriter< ::calc::Line3D>> clientStreamWriter=stub_->LargeDataSetStream(&context, &response);for (::calc::Line3D* oneLine : dataSet){          if(!clientStreamWriter->Write(*oneLine))break;   }clientStreamWriter->WritesDone();grpc::Status status = clientStreamWriter->Finish();if (status.ok()) {std::cout << "数据传输完毕" << std::endl;std::cout << "消耗时间为:" <<response.sum()<< std::endl;}else {std::cout << "数据传输失败" << std::endl;}  return ;        }private:std::unique_ptr<calc::Caltulator::Stub> stub_;
};

客户端启动代码:

int main()
{Client clinet(grpc::CreateChannel("192.168.3.91:8000",grpc::InsecureChannelCredentials()));clinet.LargeDatasetTest();return 0;
}

服务端和客户端启动,服务端接收到数据效果如下:

4.总结与讨论

通过本文的测试可以看到gRPC是可以进行大数据量消息的传输的,当然这里没有去探讨这个传输的效率,不过PB对数据的序列化压缩率很高,如果效率出现问题,那应该是在反序列化阶段,这需要进一步测试才能知道答案。其实在和同学讨论中,我们也提到了第三个解决方案,采用其他的数据传输方案(如:将数据序列化为其他形式并传输),但是在对gRPC和Protocol Buffer进一步了解后,这个方案我感觉可以否定(特别是以json来传输的提议,窃以为应该直接否掉,数据量大后,json这种松散方式数据大小更大,且对象过多,在应用的数据处理中操作更耗时)。无论采用哪种数据序列化方式,在没有考虑额外的压缩的情况下,传输过程中其实只是对数据实体的传输,数据原始是什么就是什么,是三个double构成的对象就是传三个double值(3*8byte),反序列化的时候再用接收到的值构建一个新的对象,所以不外乎就是对象à序列化à供传输的数据à反序列化à对象的过程,那么采用其他的数据传输方式就有两个问题了:不见得有PB高效且框架复杂性增加。就目前看来,采用gRPC提供的解决方案已经够用了。

参考资料

  1. https://developers.google.com/protocol-buffers/docs/techniques
  2. https://github.com/grpc/grpc/issues/7882
  3. https://nanxiao.me/en/message-length-setting-in-grpc/
  4. https://jbrandhorst.com/post/grpc-binary-blob-stream/
  5. https://blog.csdn.net/m0_37595562/article/details/80646099

gRPC大数据量消息传递方法相关推荐

  1. c++大数据量压缩方法——基于zlib的gzip解压缩方法

    在项目中用到了基于数据库的大数据量存取时,我们要考虑的是数据读取速度,及数据存储大小,往往数据存储量大的话,数据读取速度也会随之降低,所以我们降低数据存储大小的方法,一是,使用位单位来存储数据,尽量压 ...

  2. 大数据可视化的方法和价值

    数据可视化平台是是通过三维表现技术来表示复杂的信息,实现对海量数据的立体体现.可视化技术借鉴人脑的视觉展现能力,通过挖掘重要数据之间的关联关系将若干关联性的可视化数据进行汇总处理.揭示数据中隐含的关联 ...

  3. 大数据量,海量数据 处理方法总结(转)

    最近有点忙,稍微空闲下来,发篇总结贴. 大数据量的问题是很多面试笔试中经常出现的问题,比如baidu google 腾讯 这样的一些涉及到海量数据的公司经常会问到. 下面的方法是我对海量数据的处理方法 ...

  4. MySQL大数据量分页查询方法及其优化

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:收藏了!7 个开源的 Spring Boot 前后端分离优质项目个人原创+1博客:点击前往,查看更多 链接:ht ...

  5. 大数据量高并发访问的数据库优化方法

    一.数据库结构的设计 如果不能设计一个合理的数据库模型,不仅会增加客户端和服务器段程序的编程和维护的难度,而且将会影响系统实际运行的性能.所以,在一个系统开始实施之前,完备的数据库模型的设计是必须的. ...

  6. 千万级别数据查询优化_MySQL大数据量分页查询方法及其优化

    MySQL大数据量分页查询方法及其优化 ---方法1: 直接使用数据库提供的SQL语句 ---语句样式: MySQL中,可用如下方法: SELECT * FROM 表名称 LIMIT M,N ---适 ...

  7. 大数据量分页查询方法(转)

    本文旨在介绍一种对数据库中的大数据量表格进行分页查询的实现方法,该方法对应用服务器.数据库服务器.查询客户端的cpu和内存占用都较低,查询速度较快,是一个较为理想的分页查询实现方案. 1.问题的提出  ...

  8. mysql一样的查询在我本地很快但是线上很慢_MySQL大数据量分页查询方法及其优化...

    MySQL大数据量分页查询方法及其优化 ---方法1: 直接使用数据库提供的SQL语句 ---语句样式: MySQL中,可用如下方法: SELECT * FROM 表名称 LIMIT M,N ---适 ...

  9. mysql source导入大数据量时效率提升的方法

    在对mysql数据库进行大数据量导入时,我通常是选择source命令进行导入,这样能比较短的时间内导入.但是,如果数据量大到一定量级时,即使是用source命令,效率也并不高.比如此次要导入的数据库文 ...

最新文章

  1. 自定义grains_module pillar
  2. Android中的Android中的Surface和SurfaceView
  3. springboot项目中pom.xml文件的颜色变成灰色,图标变成蜘蛛图形
  4. 快来mark! 结构体重载运算符大全(运算、比较、赋值、输入输出)
  5. HALCON示例程序pcb_inspection.hdev检测pcb印刷缺陷
  6. python实例方法、类方法、静态方法的区别_Python 实例方法、类方法、静态方法的区别与作用...
  7. 判断字符为空_算法题:字符串转换整数 (atoi)
  8. 安装torch_sparse失败解决方法
  9. override和overload
  10. 回文数 详解(C++)
  11. 利用Tushare合成期货主力连续数据
  12. 亚信安全获得ISO14001和OHSAS18001双认证 提升管理软实力
  13. 利用讯飞语音听写接口实现实时语音转写。
  14. JAVA求素数和模拟条件
  15. FLASH PLAYER 谷歌浏览器浏览网站无法正常显示的问题
  16. 滴滴翻译技术探索与实践
  17. 移动产品设计书籍推荐
  18. Updates were rejected because the tip of your current branch is behind hint: its remote counterpart
  19. 蚂蚁区块链使用搭建方法
  20. OSChina 周日乱弹 ——愿你在天堂也能写代码

热门文章

  1. IOS设备与Windows面对面互传文件
  2. win7双屏幕,双任务栏
  3. 基于AST抽象语法树的SQL注入检测 (2) -- 每周小结(01-02~01-08) - .Little Hann
  4. 两行Python代码调整视频的亮度
  5. 高老师架构设计思考短句集(2)
  6. 空气质量(air quality) 简称:AQI 计算AQI等笔记
  7. office 在线预览
  8. CentOS 7.2 配置Apache服务(httpd)--上篇
  9. Double转BigDecimal并保留两位小数出现异常: java.lang.ArithmeticException: Rounding necessary
  10. Axure9.0---输入文字时文本框提示消失