Как спасти данные после искаженного фрагмента в потоке байтов, который записывается с помощью writeDelimitedTo()

Я использую protobuf-java-util:3.0.0-beta-2.

Я создаю файл, который содержит много сообщений Protobuf, которые написаны с 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]

1 ответ

Решение

Восстановить границы сообщений из необработанных данных protobuf довольно проблематично. В целом это относится к форматам данных: чем они меньше и сжаты, тем менее устойчивы к ошибкам передачи.

Лучше всего было бы, если бы в начале сообщения всегда была какая-то последовательность байтов. Например, если у вас есть почти постоянные поля, такие как required string software_version = 1;Вы можете найти это в двоичных данных и найти отправную точку следующего сообщения.

Если это не так, вы можете ввести такой маркер при написании вашего сообщения. Например, выберите 64-битное случайное число в качестве маркера. Либо вы можете записать его отдельно в файл за пределами protobuf, либо вы можете иметь fixed64 поле с индексом 1 в вашем сообщении, чтобы получить его в начале.

Даже если маркер может случайно появляться в других местах ваших данных, это не должно вызывать слишком больших проблем, поскольку вы можете просто пропустить сообщения, которые не анализируются.

Другие вопросы по тегам