【问题标题】:How does protocol buffer handle versioning?协议缓冲区如何处理版本控制?
【发布时间】:2012-01-21 02:34:11
【问题描述】:

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

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

【问题讨论】:

    标签: protocol-buffers protobuf-net


    【解决方案1】:

    Google 设计的 protobuf 对版本控制非常宽容:

    • 意外数据要么存储为“扩展”(使其往返安全),要么静默删除,具体取决于实现
    • 新字段一般添加为“可选”,表示可以成功加载旧数据

    然而:

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

    不过,一般来说 - 它可以正常工作,而且您不必太担心版本控制。

    【讨论】:

    • 大概去掉必填字段也会导致问题吧?
    • @jon 如果将数据传递给仍然认为需要它的客户端,它会这样做
    • 版本支持将是一项强大的功能,这是 Google 协议缓冲区和 Thrift 所缺乏的。我认为这个答案是在谈论与类型版本控制不同的概念(对参数错误的容忍度)。
    • @jon 出于这个原因,您最好不要将任何字段实际定义为“必需”。这个问题在 proto buffer 文档中有讨论。
    • @m-ric 我理解其中的原因,但我不确定我是否同意。通过版本控制,不仅可以引入新字段,还可以以干净的方式弃用旧字段,而不是在原型中。由于这个特定原因,Proto3 删除了“必需”,因为如果没有版本控制的概念(stackoverflow.com/questions/31801257/…),更改它们会令人困惑且有风险。自描述格式也可以通过版本控制来完成,所以我不认为它是 protobuf 的优势。
    【解决方案2】:

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

    我正在使用 Marc Gravell 的 protobuf.net (v2.3.5) 和 C#,但外观理论适用于任何语言和 Google 的原始 protobuf 实现。

    我的旧班级有一个 DateTime 时间戳,我想将其更改为包含“种类”(.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 属性,带有一个新的 proto id,并包含 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 的 required 属性,这将不起作用!!!)

        bool ShouldSerialize__Timestamp_v1() 
        {
            return __Timestamp_v1 != DateTime.MinValue;
        }
    

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

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

    【讨论】:

    • 解决老问题无需道歉。它们并没有腐烂,下一个开发者会从你的经验中受益。谢谢!
    • +1,但我不会称之为迁移。它只是一个可以在每次序列化上运行的代码,无论对象是否旧。它将来可能会爆炸,即如果该字段将再次使用。是的,proto 允许保留旧值,但在代码中保留旧的无用垃圾并不是一个好的决定。另一方面,迁移必须仅在方案版本更改且仅运行一次时运行
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-19
    • 2011-11-15
    相关资源
    最近更新 更多