当前位置:网站首页>【微服务系列】Protocol buffer动态解析

【微服务系列】Protocol buffer动态解析

2022-06-26 06:13:00 shanxiaoshuai

最近的工作中用到了grpc。之前工作中使用的是基于thrift的微服务框架,对grpc不是很熟悉,只知道grpc是一个基于http2和protobuf的rpc框架。但是使用方法都是大同小异的,基于idl生成相应的文件,服务端的话就实现具体的service并对外提供服务,客户端的话需要引入client包发起rpc调用。

这里有个问题,就是要调用下游的服务需要引入下游的client,如果下游服务的idl发生变动的话我们需要在代码中更新client包,重新编译部署发布。在常规的业务开发中这种模式是没什么问题的,发布部署的节奏是跟随业务迭代的节奏来的。但是在某些场景下可能存在比较大的问题,比如一个网关服务,网关上注册了很多的业务,网关通过rpc来调用业务服务。在这种情况下如果每次业务变更idl都需要网关随着发布部署那显然是问题很大的。

针对上面的问题进行了调研,发现可以使用动态解析idl文件的方式来解决问题。可以使用github.com/jhump/protoreflect来实现pb和grpc的反射。本来主要介绍pb部分的内容。

代码示例

建立简单的proto文件如下,其中定义了简单的结构体HelloReq和HelloResp。

syntax = "proto3";
package test;

option go_package = "data/";

message HelloReq {
    
  int64 ID = 1;
  string Name = 2;
}

message HelloResp {
    
  int64 Code = 1;
  string Message = 2;
}

按照一般的做法,这时应该执行protoc --go_out=. ./test.proto命令生成test.pb.go,然后使用该结构体做相应的操作。

下面我们使用上面提到protoreflect包来进行运行时的解析,代码如下。

package main

import (
   "encoding/json"
   "github.com/jhump/protoreflect/desc/protoparse"
   "github.com/jhump/protoreflect/dynamic"
   "grpc_practice/data"
   "log"
)

func main() {
    
   // 创建parser对象
   p := protoparse.Parser{
    }
   // 使用path的方式解析得到一些列文件描述对象,这里只有一个文件描述对象
   fileDescs, err := p.ParseFiles("./test.proto")
   if err != nil {
    
      log.Printf("parse proto file failed, err = %s", err.Error())
      return
   }
   
   // 从文件描述对象中根据消息名拿到消息藐视对象
   helloReqDesc := fileDescs[0].FindMessage("test.HelloReq")
   if helloReqDesc == nil {
    
      log.Printf("no message matched")
      return
   }
   
   // 根据消息描述对象生成动态消息
   dMsg := dynamic.NewMessage(helloReqDesc)
   
   // 使用该动态对象从GetHelloReq模拟的json对象解码
   _ = dMsg.UnmarshalJSON(GetHelloReq())
   log.Printf("req ID = %d, req Name = %s", dMsg.GetFieldByNumber(1), dMsg.GetFieldByNumber(2))

}

func GetHelloReq() []byte {
    
   req := data.HelloReq{
    }
   req.ID = 1
   req.Name = "xiaoshuai"
   d, _ := json.Marshal(&req)
   return d
}

原理解析

上面的demo还是比较简单的,看上面的代码不难发现核心的对象有两个,一是descriptor描述符,其对应了proto文件中的不同层级的对象;二是dynamicMessage动态消息,其代替了pb.go文件中的消息体。

简单来说的话,descriptor就是解析proto文件,并以数据结构的形式组织持有proto文件中的信息。dynamicMessage持有MessageDescriptor对象,就相当于了解该结构体所有信息。

Descriptor

不同层级的描述符的关系如下,没什么好详细展开的。
在这里插入图片描述

dynamicMessage


// Message is a dynamic protobuf message. Instead of a generated struct,
// like most protobuf messages, this is a map of field number to values and
// a message descriptor, which is used to validate the field values and
// also to de-serialize messages (from the standard binary format, as well
// as from the text format and from JSON).
type Message struct {
    
   md            *desc.MessageDescriptor
   er            *ExtensionRegistry
   mf            *MessageFactory
   extraFields   map[int32]*desc.FieldDescriptor
   values        map[int32]interface{
    }
   unknownFields map[int32][]UnknownField
}

dynamic message的源码如下,看到其主要的对象就是message descriptor,持有md对象dynamic message就拥有一个消息所有的信息,比如字段名称、序号、类型等。然后其值是存放在values这个map字段中,其值是以interface{}存放,所以用的时候会有大量的反射,后面会补充一些benchmark,观测其性能。

原网站

版权声明
本文为[shanxiaoshuai]所创,转载请带上原文链接,感谢
https://blog.csdn.net/shanxiaoshuai/article/details/125093083