Как спасти данные после искаженного фрагмента в потоке байтов, который записывается с помощью 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. ...
Код, который записывает эти файлы, запускается на мобильных устройствах, которые иногда получают сбой питания. В таких случаях мое приложение продолжает запись в один и тот же файл, и шансы получить эти исключения высоки. Так что, по-видимому, мой код создает в некоторых случаях искаженные файлы. Мой код может читать некоторые части файла, но не может прочитать части после искаженного фрагмента из-за ошибки.
Теперь мне нужно спасти и прочитать данные после искаженного фрагмента, но я не мог найти способ сделать это. Итак, я хотел бы знать следующее:
- Есть ли способ спасти и прочитать части, которые написаны приведенным выше кодом после уродливого фрагмента?
- Если нет способа сделать это, как я могу улучшить свой код, чтобы мое приложение могло справиться с такими проблемами? Есть ли лучшая практика, которая допускает сбой питания?
Полная трассировка стека исключений выглядит примерно так:
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 в вашем сообщении, чтобы получить его в начале.
Даже если маркер может случайно появляться в других местах ваших данных, это не должно вызывать слишком больших проблем, поскольку вы можете просто пропустить сообщения, которые не анализируются.