Protocol buffers 协议缓冲区如何处理版本控制?

Protocol buffers 协议缓冲区如何处理版本控制?,protocol-buffers,protobuf-net,Protocol Buffers,Protobuf Net,协议缓冲区如何处理类型版本控制 例如,当我需要随时间更改类型定义时?比如添加和删除字段。谷歌设计的protobuf版本控制非常宽容: 意外数据要么存储为“扩展”(使其往返安全),要么静默删除,具体取决于实现 新字段通常添加为“可选”,这意味着可以成功加载旧数据 然而: 不要对字段重新编号-这会破坏现有数据 您通常不应更改任何给定字段的存储方式(即从32位int的固定字段更改为“varint”) 不过,一般来说,它只会起作用,您不需要太担心版本控制。我知道这是一个老问题,但我最近遇到了这个

协议缓冲区如何处理类型版本控制


例如,当我需要随时间更改类型定义时?比如添加和删除字段。

谷歌设计的protobuf版本控制非常宽容:

  • 意外数据要么存储为“扩展”(使其往返安全),要么静默删除,具体取决于实现
  • 新字段通常添加为“可选”,这意味着可以成功加载旧数据
然而:

  • 不要对字段重新编号-这会破坏现有数据
  • 您通常不应更改任何给定字段的存储方式(即从32位int的固定字段更改为“varint”)

不过,一般来说,它只会起作用,您不需要太担心版本控制。

我知道这是一个老问题,但我最近遇到了这个问题。我绕过它的方法是使用facades和运行时决策来序列化。通过这种方式,我可以将字段弃用/升级为新类型,让新旧消息优雅地处理它

我使用的是Marc Gravell的protobuf.net(v2.3.5)和C#,但facades理论适用于任何语言和Google最初的protobuf实现

我的旧类有一个DateTime的时间戳,我想将其更改为包含“Kind”(一个.NET时代错误)。添加这个实际上意味着它序列化为9字节而不是8字节,这将是一个破坏性的序列化更改

    [ProtoMember(3, Name = "Timestamp")]
    public DateTime Timestamp { get; set; }
protobuf的一个基本原则是永远不要更改proto ID!我想阅读旧的序列化二进制文件,这意味着“3”将继续存在

所以

我重命名了旧属性并将其私有化(是的,它仍然可以通过反射魔法进行反序列化),但我的API不再显示它可用

    [ProtoMember(3, Name = "Timestamp-v1")]
    private DateTime __Timestamp_v1 = DateTime.MinValue;
我创建了一个新的Timestamp属性,带有一个新的protoid,并包含DateTime.Kind

    [ProtoMember(30002, Name = "Timestamp", DataFormat = ProtoBuf.DataFormat.WellKnown)]
    public DateTime Timestamp { get; set; }
对于旧消息,我添加了一个“AfterDeserialization”方法来更新我们的新时间

    [ProtoAfterDeserialization]
    private void AfterDeserialization()
    {
        //V2 Timestamp includes a "kind" - we will stop using __Timestamp - so keep it up to date
        if (__Timestamp_v1 != DateTime.MinValue)
        {
            //Assume the timestamp was in UTC - as it was...
            Timestamp = new DateTime(__Timestamp_v1.Ticks, DateTimeKind.Utc)     //This is for old messages - we'll update our V2 timestamp...
        }
    }
现在,我已经正确地序列化/反序列化了新旧消息,我的时间戳现在包括DateTime.Kind!没有坏东西

但是,这确实意味着这两个字段都将出现在今后的所有新消息中。因此,最后一点是使用运行时序列化决策来排除旧的时间戳(注意,如果使用protobuf的必需属性!!!),这将不起作用。)

就这样。我有一个很好的单元测试,如果有人想要的话,它可以从端到端进行测试


我知道我的方法依赖于.NET magic,但我认为这个概念可以翻译成其他语言……

大概,删除必需的字段也会导致问题?@jon如果将数据传递给仍然认为需要版本控制支持的客户机将是一个强大的功能,这是缺乏谷歌协议缓冲区和节俭。我认为这个答案所谈论的是一个不同于类型版本控制的概念(对参数错误的容忍)。@jon因此,最好不要将任何字段实际定义为“必需”。proto buffer文档中讨论了这个问题。@m-ric我理解其原理,但我不确定是否同意。通过版本控制,不仅可以引入新字段,而且可以以干净的方式弃用旧字段,而不是在proto中。Proto3删除了“required”是出于这个特定的原因,因为如果没有版本控制()的概念,它们会让人困惑,并且有风险进行更改。自我描述的格式也可以用版本控制来完成,所以我不认为它是一个强大的原型。这并不是说它们会腐烂,而下一个开发人员会从您的经验中获益。谢谢大家!+1,但我不会称之为迁移。它只是一个可以在每个序列化上运行的代码,不管对象是否旧。它可能在将来爆炸,也就是说,如果该领域将再次使用。是的,proto允许保留旧值,但在代码中保留旧的无用垃圾并不是一个好的决定。另一方面,迁移必须仅在更改方案版本时运行,并且仅运行一次
    bool ShouldSerialize__Timestamp_v1() 
    {
        return __Timestamp_v1 != DateTime.MinValue;
    }