JSON против буфера протокола. Какая из них лучше?

Если вы написали значительный объем кода в какой-либо веб-службе, скорее всего, вы слышали о термине JSON.

Это чрезвычайно удобный формат данных, который можно найти почти везде.

Однако в последние годы термин «буфер протокола» набирает обороты. Многие утверждали, что он превосходит JSON с точки зрения функциональности и гибкости.

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

Давайте сегодня поговорим о протокольном буфере!

Что такое буфер протокола?

Он начинается с простого вопроса.

Представьте, что у вас есть объект в программе на Python. Как отправить его в приложение JavaScript, работающее на другом компьютере?

Различные языки в различных компьютерных системах могут по-разному реализовывать объект. Это похоже на то, как разные носители языка пытаются общаться.

Следовательно, нам нужен стандартный, независимый от платформы формат данных. Одним словом, общий язык.

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

Вот что такое JSON и Protocol Buffer!

Это стандартный, независимый от платформы формат данных, упрощающий передачу данных между приложениями.

Зачем протокольный буфер?

Если JSON так хорошо справляется со своей задачей, то зачем протокольный буфер?

JSON хоть и удобен, но имеет несколько недостатков,

  1. Это формат данных без схемы
  2. Это текстовый формат данных кодирования, что приводит к большему размеру данных.
  3. Он не требует проверки на уровне схемы.

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

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


json1 = {
   "my_age_this_year": 4
}

json2 = {
   "age": 4
}
//json1 ~= 23 bytes
//json2 ~= 10 bytes

Отсутствие проверки. Поскольку JSON не содержит схемы, проверка данных может выполняться только на уровне кода.

И вот почему Protocol Buffer приходит на помощь!

Ключевая особенность

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

  • Прото файл
  • Protoc (прото-компилятор)
  • Сериализация объектов Protobuf
  • Двоичное кодирование

Прото файлы

Чтобы упростить передачу данных, приложения должны согласовать стандартную схему и формат, и это то, что представляет собой файл .proto.

Данные/объект, известный как message, определяются и сохраняются в файле .proto. Затем файл размещается как на стороне клиента, так и на стороне сервера.

Например, чтобы отправить объект пользователя между клиентом и сервером, мы определяем User message в файле .proto в следующем формате.

syntax = "proto3";

package user;

message User {
	string name = 1;
	int32 age = 2;
	int32 height = 3;
	repeated Pet pets = 4;
}

message Pet {
	string name = 1;
	string sound = 2;
} 

Файл .proto не зависит ни от платформы, ни от языков, которые вы используете.

Сообщение User указывает четыре пары ключ-значение. Каждый из них содержит

  • Тип поля - string/int32/...
  • Имя ключа - name/age/...
  • Номер поля - 1/2/...

Тип поля определяет тип значения, а термин repeated указывает, что поле представляет собой массив аналогичного типа.

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

И клиент, и сервер используют предопределенный тип message для сериализации и десериализации данных, предоставляя ProtoBuf определенную схему.

протокол

Когда вы создали свой первый файл .proto, возникает вопрос, как вы применяете его в своем приложении?

Здесь в игру вступает компилятор протоколов, также известный как Protoc!

Пользователи независимо определяют сообщение в файле .proto, а компилятор протокола компилирует его в классы любых языков, которые вы используете.

С помощью следующей команды компилятор протоколов создает файл user_pb2.py in$DST_DIR.

protoc -I=$SRC_DIR --python_out=$DST_DIR $SRC_DIR/user.proto
  • $SRC_DIR : Каталог, в котором находится исходный код
  • $DST_DIR: каталог, в котором находится сгенерированный файл _pb2.py.
  • $SRC_DIR: каталог, в котором находится исходный файл user.proto.
  • —python_out: возможность указать используемый язык
....
_USER = _descriptor.Descriptor(
name='User',
full_name='user.User',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
....

Содержимое файла user_pb2.py едва читается человеком, но это не имеет значения. Все, о чем вам нужно заботиться, это импортировать и применять эти классы в вашем приложении.

Сериализация объекта Protobuf

Давайте попробуем использовать сгенерированные классы в нашем файле user_pb2.py.

from pb.proto import user_pb2
user = user_pb2.User()
user.name = "Jason"
user.age = 20
user.height = 180
pet1 = user.pets.add()
pet1.name = "Dog"
pet1.sound = "Bark"
# Attribute error
user.friend = "Peter"
# Type error
user.age = "twenty"
protoString = user.SerializeToString()
parsedUserFromStr = user_pb2.User()
parsedUserFromStr.ParseFromString(protoString)
print(protoString)
# b'\n\x05Jason\x10\x14\x18\xb4\x01"\x0b\n\x03Dog\x12\x04Bark'
print(parsedUserFromStr)
# name: "Jason"
# age: 20
# height: 180
# pets {
#   name: "Dog"
#   sound: "Bark"
# }

Обратите внимание, что назначение данных это не просто добавление произвольного поля к типичному объекту Python.

Если вы назначите поле, не объявленное в файле .proto, или значение, несовместимое с определенным типом значения, возникнет ошибка.

С помощью встроенного метода сгенерированного класса вы можете сериализовать свои данные в строку и отправить ее в назначенное приложение.

Стоит подчеркнуть, что сериализованная строка в двоичном формате, а не в текстовом формате. str используется только как удобный контейнер для передачи данных.

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

Вот причина, по которой протокольный буфер намного компактнее, чем объект JSON.

Давайте взглянем на типичный объект JSON.

{
	"name": "Jason",
	"money": 300
}
# jsonString = '{"name": "Jason", "money": 300}'

Отправитель должен предоставить клиенту и ключ, и значение.

JSON использует текстовую кодировку, что означает, что объект преобразуется в строку текста перед отправкой, размер данных увеличивается по мере увеличения количества символов в ключе.

Protocol Buffer, с другой стороны, работает по-другому.

message Person {
	string name = 1;
	int money = 2;
}

Он заменяет ключ уникальным номером тега. Аналогичный объект, показанный выше, будет закодирован в протокольном буфере следующим образом.

125Jason20300

1 представляет номер поля, 2тип провода (тип значения), а 5 — длину строки. Обратите внимание, что только тип провода 2 требует ввода длины данных.

Аналогично, второй 2 в строке относится к номеру поля, 0 — к типу провода, а 300 — к данным.

Общий размер данных уменьшается за счет замены ключа уникальным номером поля.

Это означает, что порядок полей в файле .proto не важен. Получатель декодирует данные в соответствии с номером поля. Например, получатель назначает Jason полю с номером 1 независимо от порядка полей в файле .proto.

Однако изменение номера поля приводит к катастрофе. Если отправитель изменит номер поля name на 2, получатель данных попытается присвоить Jason переменной money вместо name.

Более того, поскольку JSON использует кодировку на основе текста, целое число 300 занимает три байта, так как содержит три символа.

00000011 00000000 00000000

С другой стороны, протокольный буфер использует двоичное кодирование и кодирует 300 в varint, который вместо этого использует 2 байта.

00000001 00101100

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

Преимущества

Прежде чем мы закончим эту статью, давайте выделим некоторые из основных преимуществ Protocol Buffer.

  • Меньше по размеру
  • Обратная совместимость
  • Схема
  • Проверка

Меньше по размеру

Как уже упоминалось, Protocol Buffer использует двоичное кодирование по сравнению с текстовым кодированием в JSON и заменяет ключ уникальным номером поля.

Следовательно, он потребляет значительно меньшее количество байтов по сравнению с JSON.

Обратная совместимость

Используя уникальный номер поля, Protocol Buffer обеспечивает превосходную обратную совместимость.

Пользователи могут легко добавлять или удалять поляна стороне отправителя/получателя, не беспокоясь о проблемах совместимости. Когда получатель декодирует данные, он

  • Игнорировать, если отправленный номер поля недоступен на стороне получателя
  • Назначьте значение по умолчанию, если требуемый номер поля недоступен в передаваемых данных

Схема

Буфер протокола обеспечивает определенную схему как на стороне отправителя, так и на стороне получателя. Это позволяет разработчикам точно знать, чего ожидать от сообщения.

Валидация данных

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

Заключение

Вот и все о Protocol Buffer!

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

На этом мы закончим. До следующего раза, чао!

Рекомендации