1

我使用 protobuf-java-util:3.0.0-beta-2。

我创建了一个文件,其中包含许多用Message#writeDelimitedTo(). 代码是这样的:

Iterable<SomeMessage> messages = getHugeDataSet();
OutputStream os = new FileOutputStream("a_lot_of_messages.protobuf");
for (SomeMessage msg : messages) msg.writeDelimitedTo(os);

我用Builder#mergeDelimitedFrom(). 像这样的东西:

InputStream is = new FileInputStream("a_lot_of_messages.protobuf");
SomeMessage msg1 = SomeMessage.newBuilder().mergeDelimitedFrom(is).build();
SomeMessage msg2 = SomeMessage.newBuilder().mergeDelimitedFrom(is).build();
... // This is simplified - it's implemented as an Iterator in the real code

这样,我可以毫无问题地读取绝大多数文件,但有时会遇到如下异常:

com.google.protobuf.InvalidProtocolBufferException: Protocol message had invalid UTF-8. ...

编写这些文件的代码在有时会断电的移动设备上运行。在这种情况下,我的应用程序会一直写入同一个文件,并且出现异常的机会很高。因此,显然我的代码在这种情况下会创建一些格式错误的文件。我的代码可以读取文件的某些部分,但由于错误,它无法读取格式错误的块之后的部分。

现在我需要在格式错误的块之后拯救和读取数据,但我找不到任何方法来做到这一点。所以,我想知道以下几点:

  1. 有什么办法可以在格式错误的块之后拯救和读取上面代码编写的部分?
  2. 如果没有办法做到这一点,我该如何改进我的代码,以便我的应用程序能够应对这些问题?有没有可以容忍电源故障的最佳实践?

完整的异常堆栈跟踪是这样的:

com.google.protobuf.InvalidProtocolBufferException: Protocol message had invalid UTF-8.
    at com.google.protobuf.InvalidProtocolBufferException.invalidUtf8(InvalidProtocolBufferException.java:120) ~[protobuf-java-3.0.0-beta-2.jar:na]
    at com.google.protobuf.CodedInputStream.readStringRequireUtf8(CodedInputStream.java:410) ~[protobuf-java-3.0.0-beta-2.jar:na]
    at com.example.Model$SomeData.<init>(Model.java:14775) ~[my-app-1.0-SNAPSHOT.jar:na]
    at com.example.Model$SomeData.<init>(Model.java:14717) ~[my-app-1.0-SNAPSHOT.jar:na]
    at com.example.Model$SomeData$1.parsePartialFrom(Model.java:18240) ~[my-app-1.0-SNAPSHOT.jar:na]
    at com.example.Model$SomeData$1.parsePartialFrom(Model.java:18234) ~[my-app-1.0-SNAPSHOT.jar:na]
    at com.google.protobuf.CodedInputStream.readMessage(CodedInputStream.java:495) ~[protobuf-java-3.0.0-beta-2.jar:na]
    at com.example.Model$SomeMessage.<init>(Model.java:27250) ~[my-app-1.0-SNAPSHOT.jar:na]
    at com.example.Model$SomeMessage.<init>(Model.java:27197) ~[my-app-1.0-SNAPSHOT.jar:na]
    at com.example.Model$SomeMessage$1.parsePartialFrom(Model.java:28678) ~[my-app-1.0-SNAPSHOT.jar:na]
    at com.example.Model$SomeMessage$1.parsePartialFrom(Model.java:28672) ~[my-app-1.0-SNAPSHOT.jar:na]
    at com.example.Model$SomeMessage$Builder.mergeFrom(Model.java:27802) ~[my-app-1.0-SNAPSHOT.jar:na]
    at com.example.Model$SomeMessage$Builder.mergeFrom(Model.java:27653) ~[my-app-1.0-SNAPSHOT.jar:na]
    at com.google.protobuf.AbstractMessageLite$Builder.mergeFrom(AbstractMessageLite.java:235) ~[protobuf-java-3.0.0-beta-2.jar:na]
    at com.google.protobuf.AbstractMessage$Builder.mergeFrom(AbstractMessage.java:516) ~[protobuf-java-3.0.0-beta-2.jar:na]
    at com.google.protobuf.AbstractMessage$Builder.mergeFrom(AbstractMessage.java:290) ~[protobuf-java-3.0.0-beta-2.jar:na]
    at com.google.protobuf.AbstractMessageLite$Builder.mergeDelimitedFrom(AbstractMessageLite.java:305) ~[protobuf-java-3.0.0-beta-2.jar:na]
    at com.google.protobuf.AbstractMessage$Builder.mergeDelimitedFrom(AbstractMessage.java:530) ~[protobuf-java-3.0.0-beta-2.jar:na]
    at com.google.protobuf.AbstractMessageLite$Builder.mergeDelimitedFrom(AbstractMessageLite.java:311) ~[protobuf-java-3.0.0-beta-2.jar:na]
    at com.google.protobuf.AbstractMessage$Builder.mergeDelimitedFrom(AbstractMessage.java:522) ~[protobuf-java-3.0.0-beta-2.jar:na]
    at com.example.MyUtils.read(MyUtils.java:54) [my-app-1.0-SNAPSHOT.jar:na]
4

1 回答 1

1

从原始 protobuf 数据中恢复消息边界是很成问题的。这通常适用于数据格式,它越小越压缩,它对传输错误的弹性就越小。

最好的方法是,如果有一些字节序列总是出现在消息的开头。例如,如果您碰巧有任何几乎恒定的字段,例如required string software_version = 1;,您可以在二进制数据中搜索它,从而找到下一条消息的起点。

如果不是这种情况,您可以在编写消息时引入这样的标记。例如,选择一些 64 位随机数作为标记。您可以将其单独写入 protobuf 之外的文件,也可以fixed64在消息中包含索引为 1 的字段以使其接近开头。

即使标记可能随机出现在数据的其他地方,也不应该导致太大的问题,因为您可以跳过不解析的消息。

于 2017-11-15T06:13:16.440 回答