背景
最近在做的项目准备通过grpc进行通信,server端返回的数据类型是不固定的(通过go语言访问redis 中执行redis命令返回的数据类型是interface{}),所以我的诉求是client层可以通过grpc接收到这种泛类型。
不将解析结果集的逻辑放在server层主要是考虑了两个方面
- 执行redis命令返回的数据类型包括多种,其中包括嵌套数组,相应的protoc文件中消息结构体定义会变得比较复杂
- server层作为agent,如果将过多的逻辑放到这一层,后期的迭代更新成本会较大
有两种解决思路
备注:方法1在我这个场景下行不通,但是也记录以下大概的实践方法
1. 使用proto3的Any定义泛类型
protocol文件中通过Any定义泛类型字段
import "google/protobuf/any.proto";
message AgentExecRedisRes{
google.protobuf.Any AgentLocalExecRedisCmdRes = 1;
server 端处理客户端rpc请求的伪代码
- Any字段需要通过MarshalAny(m proto.Message) (anypb.Any, error)方法转换为anypb.Any类型,然后grpc将其传给client端,client端再通过ptypes.UnmarshalAny(any *anypb.Any, m proto.Message) 转换成protoc3支持的类型
- server端转成*anypb.Any的条件是你传入的类型必须要是protoc3支持的数据类型,所以又到了绕不开的这一个问题了:需要在server层完成数据解析
any "github.com/golang/protobuf/ptypes/any"
// 单次rpc交互函数,用于agent-server单次交互,同步交互
func (server *ProxyAgentServer) ReceiveSyncTaskMsgFromServer(ctx context.Context,
taskMsg *agentpb.ManagerTaskMsg) (*agentpb.ManagerTaskRespons, error) {
var err error
var reply interface{
}
// 构造rpc返回消息
responsMsg := &agentpb.ManagerTaskRespons{
Timestamp: time.Now().UnixNano(),
TaskId: taskMsg.TaskId,
TaskType: taskMsg.GetTaskType(),
ErrorCode: 0,
ErrorMsg: "",
}
switch taskMsg.GetTaskType() {
case agentpb.TaskType_REDIS_CMD_EXEC:
reply, err = worker.RedisCmdExec(taskMsg.GetTaskExecRedis().GetServerType(),
taskMsg.GetTaskExecRedis().GetPort(), taskMsg.GetTaskExecRedis().GetRedisCmd())
if err != nil {
common.Log.Warn("Failed to execute redis cmd task taskid=[%v] reason=[%v]",
taskMsg.GetTaskId(), err)
} else {