[Перевод] Двоичное‌ ‌кодирование‌ ‌вместо‌ ‌JSON

Кодируйте‌ ‌одни‌ ‌и‌ ‌те‌ ‌же‌ ‌данные‌ ‌гораздо‌ ‌меньшим‌ ‌количеством‌ ‌байт.‌ ‌

image

Почему‌ ‌меня‌ ‌это‌ ‌должно‌ ‌волновать ‌


Данные‌ ‌хранятся‌ ‌в‌ ‌памяти‌ ‌в‌ ‌виде‌ ‌структур‌ ‌данных, ‌ ‌таких‌ ‌как‌ ‌объекты, ‌ ‌списки, ‌ ‌массивы‌ ‌и‌ ‌т.д.‌ ‌Но‌ ‌если‌ ‌вы‌ ‌хотите‌ ‌отправить‌ ‌данные‌ ‌по‌ ‌сети‌ ‌или‌ ‌в‌ ‌файле, ‌ ‌вам‌ ‌нужно‌ ‌закодировать‌ ‌их‌ ‌в‌ ‌виде‌ ‌последовательности‌ ‌байтов.‌ ‌Перевод‌ ‌из‌ ‌представления‌ ‌в‌ ‌памяти‌ ‌в‌ ‌последовательность‌ ‌байтов‌ ‌называется‌ ‌кодированием, ‌ ‌а‌ ‌обратное‌ ‌преобразование‌ ‌–‌ ‌декодированием.‌ ‌Со‌ ‌временем‌ ‌схема‌ ‌данных, ‌ ‌обрабатываемых‌ ‌приложением‌ ‌или‌ ‌хранящихся‌ ‌в‌ ‌памяти, ‌ ‌может‌ ‌эволюционировать, ‌ ‌туда‌ ‌могут‌ ‌добавляться‌ ‌новые‌ ‌поля‌ ‌или‌ ‌удаляться‌ ‌старые.‌ ‌Следовательно, ‌ ‌используемая‌ ‌ кодировка‌ ‌должна‌ ‌иметь‌ ‌как‌ ‌обратную‌ ‌(новый‌ ‌код‌ ‌должен‌ ‌быть‌ ‌способен‌ ‌читать‌ ‌данные, ‌ ‌написанные‌ ‌старым‌ ‌кодом), ‌ ‌так‌ ‌и‌ ‌прямую‌ ‌(старый‌ ‌код‌ ‌должен‌ ‌быть‌ ‌способен‌ ‌читать‌ ‌данные, ‌ ‌написанные‌ ‌новым‌ ‌кодом)‌ ‌совместимость.‌ ‌

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

Типы‌ ‌форматов‌ ‌кодирования‌


Существует‌ ‌два‌ ‌типа‌ ‌форматов‌ ‌кодирования: ‌ ‌

  • Текстовые‌ ‌форматы‌ ‌
  • Двоичные‌ ‌форматы‌

‌ ‌

Текстовые‌ ‌форматы‌

‌ ‌Текстовые форматы в некоторой степени человекочитаемы. Примеры распространенных форматов — JSON, CSV и XML. Текстовые форматы просты в использовании и понимании, но имеют определенные проблемы:

  • Текстовые форматы могут быть очень неоднозначны. Например, в XML и CSV нельзя различать строки и числа. JSON может различать строки и числа, но не может различать целые и вещественные числа, а также не задает точности. Это становится проблемой при работе с большими числами. Так, проблема с числами, большими чем 253 встречается в Twitter, который использует 64-битное число для идентификации каждого твита. JSON, возвращаемый API Twitter, включает в себя ID твита дважды — в виде JSON-числа и в виде десятичной строки — все из-за того, что JavaScript-приложения не всегда верно распознают числа.
  • CSV не имеет схемы данных, возлагая определение значения каждой строки и столбца на приложение.
  • Текстовые форматы занимают больше места, чем двоичная кодировка. Например, одна из причина заключается в том, что JSON и XML не имеют схемы, а потому они должны содержать имена полей.
{
    "userName": "Martin",
    "favoriteNumber": 1337,
    "interests": ["daydreaming", "hacking"]
}

JSON-кодировка этого примера после удаления всех белых пробелов занимает 82 байта.

Двоичное кодирование


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

  1. Thrift
  2. Protocol Buffers
  3. Avro

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

Thrift и Protocol Buffers


Thrift разработан Facebook, а Protocol Buffers — Google. В обоих случаях для кодирования данных требуется схема. В Thrift схема определяется с помощью собственного языка определения интерфейса (IDL).

struct Person {
  1: string       userName,
  2: optional i64 favouriteNumber,
  3: list interests
}

Эквивалентная схема для Protocol Buffers:

message Person {
    required string user_name        = 1;
    optional int64  favourite_number = 2;
    repeated string interests        = 3;
}

Как видите, у каждого поля имеется тип данных и номер тега (1, 2 и 3). У Thrift есть два различных формата двоичной кодировки: BinaryProtocol и CompactProtocol. Двоичный формат прост, как показано ниже, и занимает 59 байт для кодирования данных, приведенных выше.

image

Кодирование с использованием двоичного протокола Thrift

Компактный протокол семантически эквивалентен бинарному, но упаковывает одну и ту же информацию всего в 34 байта. Экономия достигается за счет упаковки типа поля и номера метки в один байт.

image

Кодирование с использованием протокола Thrift Compact

Protocol Buffers кодирует данные аналогично компактному протоколу в Thrift, и после кодирования эти же данные занимают 33 байта.

image

Кодирование с использованием Protocol Buffers

Номера тегов обеспечивают эволюцию схем в Thrift и Protocol Buffers. Если старый код попытается прочитать данные, записанные с новой схемой, он просто проигнорирует поля с новыми номерами тегов. Аналогично, новый код может прочитать данные, записанные по старой схеме, пометив значения как null для пропущенных номеров тегов.

Avro


Avro отличается от Protocol Buffers и Thrift. Avro также использует схему для определения данных. Схему можно определить, используя IDL Avro (человекочитаемый формат):

record Person {
    string               userName;
    union { null, long } favouriteNumber;
    array        interests;
}

Или JSON (более машиночитаемый формат):

"type": "record",
    "name": "Person",
    "fields": [
        {"name": "userName",        "type": "string"},
        {"name": "favouriteNumber", "type": ["null", "long"]},
        {"name": "interests",       "type": {"type": "array",      "items": "string"}}
    ]
}

Обратите внимание, что у полей нет номеров меток. Те же самые данные, закодированные с помощью Avro, занимают всего 32 байта.

image

Кодирование с помощью Avro.

Как видно из вышеприведенной последовательности байт, поля не могут быть идентифицированы (в Thrift и Protocol Buffers для этого используются метки с номерами), также невозможно определить тип данных поля. Значения просто собираются воедино. Означает ли это, что любое изменение схемы при декодировании будет генерировать некорректные данные? Ключевая идея Avro заключается в том, что схема для записи и чтения не обязательно должна быть одинаковой, но должна быть совместимой. Когда данные декодируются, библиотека Avro решает эту проблему, просматривая обе схемы и транслируя данные из схемы записывающего устройства в схему читающего устройства.

image

Устранение различий между схемой читающего и записывающего устройства

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

  1. При передаче больших файлов или данных, записывающее устройство может однократно включить схему в начало файла.
  2. В БД с индивидуальными записями каждая строка может быть записана со своей схемой. Самое простое решение — указывать номер версии в начале каждой записи и сохранять список схем.
  3. Для отправки записи в сети, читающее и записывающее устройства могут согласовать схему при установке соединения.

Одним из основных преимуществ использования формата Avro является поддержка динамически генерируемых схем. Так как метки с номерами не генерируются, вы можете использовать систему контроля версий для хранения различных записей, закодированных с разными схемами.

Заключение


В этой статье мы рассмотрели текстовые и двоичные форматы кодирования, обсудили как одни и те же данные могут занимать 82 байта с кодировкой JSON, 33 байта с кодировкой Thrift и Protocol Buffers, и всего 32 байта с помощью кодировки Avro. Двоичные форматы предлагают несколько неоспоримых преимуществ по сравнению с JSON при передаче данных в сети между внутренними службами.

Ресурсы


Чтобы узнать больше о кодировках и проектировании приложений c интенсивной обработкой данных, я настоятельно рекомендую прочитать книгу Мартина Клеппмана «Designing Data-Intensive Applications».

image

Узнайте подробности, как получить востребованную профессию с нуля или Level Up по навыкам и зарплате, пройдя платные онлайн-курсы SkillFactory:

Читать еще


© Habrahabr.ru