gRPC是一个远程调用框架,使用Protobuf做为信息的载体来完成客户端和服务端的数据传输。关于怎么定义Protobuf消息、搭建gRPC服务在之前的系列文章中都有提及,今天来说一下在使用gRPCProtobuf的过程中怎么传递动态参数。

首先说明一下,这里所说的动态参数指的是在定义Protobuf消息时还不能确定其具体内容的复合类型字段,简单的说就是消息里的这个字段我们想传一个类似JSON对象、Map字典、结构体等等这样的组合值,但是JSON里有哪些字段、每个字段值是什么类型或者Map字典键值的类型我们在定义消息时还无法确定(能确定就可以定义子消息嵌套进来了,不在本文的讨论范围内),把这样的Protobuf消息字段叫做动态参数。

针对通过Protobuf传递动态参数的需求,官方文档里并没有给出标准的解决方案,目前我所知道的能够通过bytesMap<string, string>以及proto.Struct这三种Protobuf消息字段的类型实现,每种方式也都有自己的优势和劣处,如果你碰巧知道更好的实现方案,欢迎在评论里留言讨论。

下面我们就来看一下使用这三种消息字段的类型如何实现动态参数的传递。

使用bytes传递JSON对象参数

Protobuf里的bytes类型的字段编码成Go代码后对应的是Go里的字节切片[]byte类型。所以我们可以把动态参数的字段类型定义成bytes类型,这样客户端把JSON对象传递到服务端后,服务端能直接对动态参数里包含的JSON对象做解码操作,省去了一次从string[]byte的类型转换。

举个例子来说,在下面的Protobuf消息定义里info字段的类型是bytes

rpc UpdateRecord (UpdateRecordRequest) returns (UpdateRecordReply) {
}message UpdateRecordRequest {int64 id = 1;bytes info = 2;
}

那么在使用对于这个gRPC方法,客户端在使用的时候,直接把info数据通过json.Marshal编码后传递给服务端即可。

info := struct {name stringage  int
} {name: "James",age: 20,
}
jsonInfo, _ := json.Marshal(info)
_ := AppService.UpdateRecord(&AppService.UpdateRecordRequest{id: 2, info: jsonInfo})

在服务端可以加一个参数验证,保证传递过来的是一个正确的JSON对象。

func IsJSON(in []byte) bool {var js map[string]interface{}return json.Unmarshal(in, &js) == nil}

验证完后就可以根据实际的使用需求解码动态参数里的JSON对象解析到具体的结构体变量。

type Info struct {name string `json:"name"`age int `json:"id"`
}func (s server) UpdateRecord(ctx context.Context, reqeust *AppService.UpdateRecordRequest) (reply *AppService.UpdateRecordReply, err error) {if !isJson(req.Info) {// 错误处理...}v := Info{}json.Unmarshal(req.Info, $v)
}

我一般是这种方法,感觉比较方便,唯一算是麻烦的地方就是每个使用动态参数的地方要自己定义解析JSON对象对应的结构体类型。

使用Map类型传递动态参数

如果你不想通过JSON对象来传递参数,另一种经常能想到的方案是把参数的字段类型定义成字典,具体每次调用时可以根据需要设置不同的Key-Value对。Protobuf恰好也有Map类型。

map<key_type, value_type> map_field = N;

但是有一点,在定义Map类型时,值的类型必须是固定的,并不支持像map[string]interface{}这样的值类型。所以这种方式一般是在能确定字典参数的值类型时使用,否则如果定义成了map<string, string>的话假如要传递整型的字段,客户端还需要先将数据从整型转换成字符串类型。

使用proto.Struct传递结构体动态参数

有些资料里提到了使用Protobuf里自带了一个复合类型proto.Struct传递动态类型参数,使用它的好处是它看起来是Protobuf对动态类型数据的一种原生支持,可以使用Protobuf自带的包jsonpb 完成从JSONproto.Struct之间的转换。

使用proto.Struct类型需要在proto文件里先引入它的类型定义,像下面这样。

syntax = "proto3";
package messages;
import "google/protobuf/struct.proto";service UserService {rpc SendJson (SendJsonRequest) returns (SendJsonResponse) {}
}message SendJsonRequest {string UserID = 1;google.protobuf.Struct Details = 2;
}message SendJsonResponse {string Response = 1;
}

通过proto.Struct的源码定义能看到它底层其实是一个名叫Struct的消息,里面只包含了一个名叫filedsMap类型字段,通过ProtobufOneof特性指定了Map值的类型范围来近似完成了动态类型的支持。

message Struct {// Unordered map of dynamically typed values.map<string, Value> fields = 1;
}message Value {// The kind of value.oneof kind {// Represents a null value.NullValue null_value = 1;// Represents a double value.double number_value = 2;// Represents a string value.string string_value = 3;// Represents a boolean value.bool bool_value = 4;// Represents a structured value.Struct struct_value = 5;// Represents a repeated `Value`.ListValue list_value = 6;}
}

所以在使用的时候操作proto.Struct有点像操作字典,下面是一个使用的示例。

func sendJson(userClient pb.UserServiceClient, ctx context.Context) {var item = &structpb.Struct{Fields: map[string]*structpb.Value{"name": &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "James",},},"age": &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: 20,},},},}userGetRequest := &pb.SendJsonRequest{UserID: "A123",Details: item,}res, err := userClient.SendJson(ctx, userGetRequest)
}

总结

三种方法总结下来我还是觉得第一种使用起来更方便,第二种只能把值类型局限为一种,否则就需要在客户端和服务端做类型转换,第三种也是网上能找到对proto.Struct的使用的资料较少,上手难度较大,且也不如第一种灵活。另外Protobuf还有一个Any类型,让我们使用的时候不需要定义消息,但是要携带一个说明数据的url使用起来感觉也不太方便。这块如果读者朋友们相关经验可以一起来探讨一下。

最后做一个投票,针对gRPC动态参数这种需求大家都是怎么解决?

推荐阅读:gRPC服务注册发现及负载均衡的实现方案与源码解析

- END -

关注公众号,每周帮你学会一个进阶知识

三种传递gRPC动态参数方式的使用体验相关推荐

  1. iframe的src怎么携带参数_三种传递gRPC动态参数方式的使用体验

    gRPC是一个远程调用框架,使用Protobuf做为信息的载体来完成客户端和服务端的数据传输.关于怎么定义Protobuf消息.搭建gRPC服务在之前的系列文章中都有提及,今天来说一下在使用gRPC和 ...

  2. Extjs中三种不同的数据提交方式

    Extjs中三种不同的数据提交方式 Extjs的三种提交方式: 表单Ajax提交,普通提交,单独Ajax提交: 1.表单ajax提交(默认提交方式) 提交函数:当按下表单中的提交按钮时执行下面的btn ...

  3. 什么是动态代理?两种常用的动态代理方式

    什么是动态代理? 动态代理就是,在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术.在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法.可以 ...

  4. 一、三种基本的共享上网方式:

    一.三种基本的共享上网方式: 总的来说,目前的共享上网方式不外乎:网关型共享上网.代理服务器型共享上网和路由共享上网三种.下面分别予以简单介绍. 1. 网关型共享上网: 网关型共享上网,就把直接连接互 ...

  5. android系统密码设置功能,手机锁屏密码怎么设置 三种安卓手机锁屏方式推荐

    手机中有很多应用都是与金钱挂钩,特别是微信与支付宝等等既涉及到隐私又与财产关联,这是后手机的安全就尤为重要的,而手机的锁屏密码就是一道最基本的防护措施,那么手机锁屏密码怎么设置?来看看小编推荐的三种安 ...

  6. 【redis】三种redis数据导出导入方式

    文章目录 1.概述 一.redis-dump方式 二.aof方式导入 三.rdb文件迁移方式 1.概述 转载:三种redis数据导出导入方式 一.redis-dump方式 redis-dump安装 y ...

  7. 三种主流的Kubernetes部署方式

    [译者的话]本文分析介绍了三种主流的Kubernetes部署方式,为广大Kubernetes的使用者提供了很好的参考借鉴. [烧脑式Kubernetes实战训练营]本次培训理论结合实践,主要包括:Ku ...

  8. html 三种插入css样式的方式

    三种插入css样式的方式 一.外链式引入css样式 二.内嵌式使用css样式 三.行内式引入css样式 一.外链式引入css样式 在head标签中使用<link rel="styles ...

  9. 三种让div居中的方式

    三种让div居中的方式 写在前面:div居中于屏幕中间有很多种方式,这里就记录下 学习的时候的三种方式 毕竟面试题问的多!! 1.宽度和高度已知的 <!DOCTYPE html> < ...

最新文章

  1. 10家最具创新性的机器学习公司
  2. 10、如何查看MySQL系统帮助?
  3. 4 个场景揭秘,如何低成本让容器化应用 Serverless 化?
  4. Redis 五种数据结构以及三种高级数据结构解析以及使用
  5. VMware / 三种联网方法及原理
  6. Opencv中的阈值函数
  7. linux(4):Linux逻辑卷详解总结
  8. udp协议的服务器是哪种类型,UDP协议
  9. SQLite | Select 语句
  10. Linux curl命令使用代理、以及代理种类介绍(附:curl命令详解)
  11. 日本研发投篮机器人Cue,投球命中率接近100%
  12. 《锋利的jQuery》随笔(一)
  13. Hibernate不能自动建表解决办法【转载】
  14. OMRON欧姆龙Sysmac Studio软件--ESI文件的安装
  15. 哪个邮箱好用?!TOM邮箱品牌测评分析
  16. Linux从零搭建web服务器
  17. 云端赋能安全驱动,知道创宇2021新品发布季强势来袭!
  18. 自己动手做一个绿色便携版的谷歌浏览器Chrome,可以放入U盘随意带走的
  19. jQuery(入门)
  20. node os模块读取hostname乱码

热门文章

  1. SCRF的简介及防护手段
  2. URL加载系统之三:NSURLConnection
  3. 自动生成宣传单打印页--提高工作效率
  4. How to Plan My Life?
  5. python-object-twoxml-html_1
  6. 关于中层管理者的会议态度
  7. 创建一个简单的ArcGIS Server ASP.NET网页
  8. ajax 使用方法简述
  9. 深入分析Java ClassLoader原理
  10. Android——DDMS简单介绍