Protobuf-Unknown字段

待解决的问题:

分布式系统中,各个模块接口之间proto文件在升级过程中,必然会存在版本不一致的情况。
unknown字段,用于解决proto文件升级过程中,在多级联关系的各个模块(特别是涉及路由功能模块传递数据时)接口之间proto版本不一致,而导致数据无法传递的问题。

例如:

之前在hy-new-router重构项目开发中,就遇到这样的问题。利用driver向asp发送消息,消息到了hy-ui解析失败。
Asp/hy-router/hy-ui 3个模块之间通信是使用厂内历史留存的idl方式定义的,按照包含字段的内容,v1>v3>v2(v2的数据内容最少,虽然看上去各个字段差异部分都是使用了optional方式做了兼容),hy-router按照v2解析后,传递给下游hy-ui。

avatar

解决的思路:

parse数据时,如果发现某个field_id不在本模块接口定义proto中,那么将这个field保存到unknown字段中,在后续处理 serialize过程中,会将unkown字段继续传递下去。

具体技术实现:

类UnknownField

记录unknown字段的key(field_id,数据类型)和value。针对不同type数据,使用union方式实现。涉及到非primitive类型数据,考虑了DeepCopy。

key:

1
2
3
4
5
6
7
8
9
10
enum Type {
TYPE_VARINT,
TYPE_FIXED32,
TYPE_FIXED64,
TYPE_LENGTH_DELIMITED,
TYPE_GROUP
};

unsigned int number_ : 29;
unsigned int type_ : 3;

value:

1
2
3
4
5
6
7
union {
uint64 varint_;
uint32 fixed32_;
uint64 fixed64_;
string* length_delimited_;
UnknownFieldSet* group_;
};

类UnknownFieldSet

vector<UnknownField> 方式记录UnknownField。

message处理中的实现

所有具体message子类中,包含了对应unkown字段以及访问方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

class LIBPROTOC_EXPORT CodeGeneratorRequest : public ::google::protobuf::Message {

public:
inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const {
return _unknown_fields_;
}

…… //省略

private:

::google::protobuf::UnknownFieldSet _unknown_fields_;
…… //省略

}

message对应的reflection也可以访问到对应unknown字段,访问方式和其它reflection功能一样,通过base + offset偏移方式获取到对应内存地址,然后reinterpret_cast。

1
2
3
4
5
6
const UnknownFieldSet& GeneratedMessageReflection::GetUnknownFields(
const Message& message) const {
const void* ptr = reinterpret_cast<const uint8*>(&message) +
unknown_fields_offset_;
return *reinterpret_cast<const UnknownFieldSet*>(ptr);
}

这里unknown_fields_offset_是在构造GeneratedMessageReflection时传递的,在每个由protoc产生的pb.cc中都会有,例如compiler/plugin.pb.cc 中:

1
2
3
4
5
6
7
8
9
10
11
12

CodeGeneratorRequest_reflection_ =
new ::google::protobuf::internal::GeneratedMessageReflection(
CodeGeneratorRequest_descriptor_,
CodeGeneratorRequest::default_instance_,
CodeGeneratorRequest_offsets_,
GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(CodeGeneratorRequest, _has_bits_[0]),
GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(CodeGeneratorRequest, _unknown_fields_),
-1,
::google::protobuf::DescriptorPool::generated_pool(),
::google::protobuf::MessageFactory::generated_factory(),
sizeof(CodeGeneratorRequest));

parse数据过程中,代码调用关系梳理如下。

  1. MessageLite::ParseFromString() ->
  2. InlineParseFromArray() ->
  3. InlineMergeFromCodedStream() ->
  4. Message::MergePartialFromCodedStream() ->
  5. WireFormat::ParseAndMergePartial() ->
  6. WireFormat::ParseAndMergeField() ->
  7. WireFormat::SkipField()

核心部分在WireFormat::ParseAndMergePartial() 开始:

  1. while循环中从io::CodedInputStream* input中逐个读取tag;
  2. 从tag提取field_number,由field_numberDescriptor*查找FieldDescriptor*;
  3. 如果找不到field_number,WireFormat::ParseAndMergeField()中获取Reflection* message_reflection,再通过GeneratedMessageReflection::GetUnknownFields()获得unknown字段;
  4. WireFormat::SkipField()中,根据field_type,调用UnknownFieldSet不同方法;