使用protobuf-net处理大文件的最佳PrefixStyle选择及方案验证
关于protobuf-net 2.4.0处理GB级大数据序列化的疑问
初始场景
我目前用protobuf-net 2.4.0把GB级数据存到流里,当前策略是给小头部用PrefixStyle.Base128的LengthPrefix,主体用标准protobuf序列化,代码运行正常:
private void Serialize(Stream stream) { Model.SerializeWithLengthPrefix(stream, FileHeader, typeof(FileHeader), PrefixStyle.Base128, 1); if (FileHeader.SerializationMode == serializationType.Compressed) { using (var gzip = new GZipStream(stream, CompressionMode.Compress, true)) using (var bs = new BufferedStream(gzip, GZIP_BUFFER_SIZE)) { Model.Serialize(bs, FileBody); } } else Model.Serialize(stream, FileBody); }
现在要把主体拆成两个不同对象,所以也得给它们用LengthPrefix方案,但不确定哪种PrefixStyle最合适:
- 能不能继续用
Base128? PrefixStyle.Fixed32描述里的“有助于兼容性”是什么意思?
更新:标记与LengthPrefix结合的方案可行性
我看到有提到可以用起始和结束标记,但不确定能不能和LengthPrefix结合用。更具体地说,下面的代码方案是否有效?
[ProtoContract] public class FileHeader { [ProtoMember(1)] public int Version { get; } [ProtoMember(2)] public string Author { get; set; } [ProtoMember(3)] public string Organization { get; set; } } [ProtoContract(IsGroup = true)] // IsGroup=true对大数据的LengthPrefix有帮助吗? public class FileBody1 { [ProtoMember(1), DataFormat = DataFormat.Group] public List<Foo1> Foo1s { get; } [ProtoMember(2), DataFormat = DataFormat.Group] public List<Foo2> Foo2s { get; } [ProtoMember(3), DataFormat = DataFormat.Group] public List<Foo3> Foo3s { get; } } [ProtoContract(IsGroup = true)] // IsGroup=true对大数据的LengthPrefix有帮助吗? public class FileBody2 { [ProtoMember(1), DataFormat = DataFormat.Group] public List<Foo4> Foo4s { get; } [ProtoMember(2), DataFormat = DataFormat.Group] public List<Foo5> Foo5s { get; } [ProtoMember(3), DataFormat = DataFormat.Group] public List<Foo6> Foo6s { get; } } public static class Helper { private static void SerializeFile(Stream stream, FileHeader header, FileBody1 body1, FileBody2 body2) { var model = RuntimeTypeModel.Create(); var serializationContext = new ProtoBuf.SerializationContext(); model.SerializeWithLengthPrefix(stream, header, typeof(FileHeader), PrefixStyle.Base128, 1); model.SerializeWithLengthPrefix(stream, body1, typeof(FileBody1), PrefixStyle.Base128, 1, serializationContext); model.SerializeWithLengthPrefix(stream, body2, typeof(FileBody2), PrefixStyle.Base128, 1, serializationContext); } private static void DeserializeFile(Stream stream, ref FileHeader header, ref FileBody1 body1, ref FileBody2 body2) { var model = RuntimeTypeModel.Create(); var serializationContext = new ProtoBuf.SerializationContext(); header = model.DeserializeWithLengthPrefix(stream, null, typeof(FileHeader), PrefixStyle.Base128, 1) as FileHeader; body1 = model.DeserializeWithLengthPrefix(stream, null, typeof(FileBody1), PrefixStyle.Base128, 1, null, out _, out _, serializationContext) as FileBody1; body2 = model.DeserializeWithLengthPrefix(stream, null, typeof(FileBody2), PrefixStyle.Base128, 1, null, out _, out _, serializationContext) as FileBody2; } }
如果这个方案有效,我是不是不用关心前缀长度(也就是表示消息长度的标记),可以继续存储大数据?
解答
1. PrefixStyle选择:Base128完全可用
你完全可以继续使用PrefixStyle.Base128处理拆分后的两个主体对象:
- Base128是protobuf原生的变长编码,对小数据更省空间,同时它支持64位长度编码,完全能覆盖GB级甚至TB级的数据大小需求。
- 它的性能和Fixed32相当,多数场景下表现更优,不会浪费字节。
2. PrefixStyle.Fixed32的“兼容性”含义
Fixed32用固定4字节存储长度,所谓的兼容性是指:
- 部分非protobuf原生的解析工具,可能只支持固定长度的前缀格式,无法解析变长的Base128编码。
- 如果你需要和这类第三方工具交互,Fixed32更合适;但如果只是用protobuf-net自身做序列化/反序列化,Base128是更优选择。
3. 提供的代码方案有效性分析
你的代码方案完全有效,但有几个细节可以优化:
- 不要每次序列化/反序列化都创建
RuntimeTypeModel:RuntimeTypeModel.Create()会生成新模型,重复创建会浪费性能,建议全局复用一个单例模型。 IsGroup = true和DataFormat.Group对你当前的LengthPrefix方案无帮助:Group格式是protobuf的“标记结束”式序列化,不需要长度前缀,和LengthPrefix是二选一的关系。同时使用反而会增加额外序列化开销,建议去掉这些标记,保持LengthPrefix的简洁性。
4. 大数据存储的前缀长度问题
不用担心前缀长度的问题:protobuf-net的SerializeWithLengthPrefix用Base128编码时,支持的最大长度为Int64.MaxValue(约9EB),完全能覆盖GB级数据的存储需求,只要单个对象序列化后的大小不超过这个值就不会有问题。
内容的提问来源于stack exchange,提问作者ilCosmico
相关产品推荐
相关产品推荐

