Обзор

Protocol Buffers - это независимый от языка и платформы расширяемый механизм для сериализации структурированных данных.

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

Protocol Buffers — это комбинация языка определений (создаваемого в файлах .proto), кода, который компилятор proto генерирует для работы с данными, специфичных для языка библиотек времени выполнения, формата сериализации для данных, которые записываются в файл (или отправляются по сетевому соединению), и самих сериализованных данных.

Какие проблемы решают Protocol Buffers?

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

Protocol Buffers — наиболее часто используемый формат данных в Google. Они широко используются в межсерверных коммуникациях, а также для архивного хранения данных на диске. Сообщения и сервисы Protocol Buffers описываются инженерами в файлах .proto. Ниже показан пример message:

edition = "2023";

message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;
}

Компилятор proto запускается во время сборки для файлов .proto, чтобы сгенерировать код на различных языках программирования (рассматривается в разделе Кросс-языковая совместимость далее в этой теме) для манипуляции соответствующим protocol buffer. Каждый сгенерированный класс содержит простые методы доступа для каждого поля и методы для сериализации и разбора всей структуры в сырые байты и из них. Ниже показан пример использования этих сгенерированных методов:

Person john = Person.newBuilder()
    .setId(1234)
    .setName("John Doe")
    .setEmail("jdoe@example.com")
    .build();
output = new FileOutputStream(args[0]);
john.writeTo(output);

Поскольку Protocol Buffers широко используются во всех видах сервисов в Google и данные внутри них могут сохраняться в течение длительного времени, поддержание обратной совместимости имеет crucial. Protocol Buffers позволяют беспрепятственно поддерживать изменения, включая добавление новых полей и удаление существующих полей, для любого protocol buffer без нарушения работы существующих сервисов. Подробнее об этой теме см. Обновление определений Proto без обновления кода далее в этой теме.

Каковы преимущества использования Protocol Buffers?

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

Некоторые преимущества использования Protocol Buffers включают:

  • Компактное хранение данных
  • Быстрый разбор
  • Доступность на многих языках программирования
  • Оптимизированная функциональность через автоматически сгенерированные классы

Кросс-языковая совместимость

Одни и те же сообщения могут быть прочитаны кодом, написанным на любом поддерживаемом языке программирования. Вы можете иметь Java-программу на одной платформе, захватывающую данные из одной программной системы, сериализующую их на основе определения .proto, а затем извлекающую определенные значения из этих сериализованных данных в отдельном Python-приложении, работающем на другой платформе.

Следующие языки поддерживаются непосредственно в компиляторе protocol buffers, protoc:

Следующие языки поддерживаются Google, но исходный код проектов находится в репозиториях GitHub. Компилятор protoc использует плагины для этих языков:

Дополнительные языки не поддерживаются напрямую Google, а другими проектами на GitHub. Эти языки рассматриваются в Сторонние дополнения для Protocol Buffers.

Поддержка межпроектного взаимодействия

Вы можете использовать Protocol Buffers в разных проектах, определяя типы message в файлах .proto, которые находятся вне базового кода конкретного проекта. Если вы определяете типы message или перечисления (enums), которые, как вы ожидаете, будут широко использоваться за пределами вашей непосредственной команды, вы можете поместить их в свой собственный файл без зависимостей.

Пару примеров широко используемых в Google определений proto — это timestamp.proto и status.proto.

Обновление определений Proto без обновления кода

Стандартной практикой для программных продуктов является обратная совместимость, но менее распространена прямая совместимость. До тех пор, пока вы следуете некоторым простым практикам при обновлении определений .proto, старый код будет читать новые сообщения без проблем, игнорируя любые вновь добавленные поля. Для старого кода удаленные поля будут иметь свое значение по умолчанию, а удаленные повторяющиеся (repeated) поля будут пустыми. Для получения информации о том, что такое «повторяющиеся» поля, см. Синтаксис определений Protocol Buffers далее в этой теме.

Новый код также будет прозрачно читать старые сообщения. Новые поля не будут присутствовать в старых сообщениях; в этих случаях Protocol Buffers предоставляют разумное значение по умолчанию.

Когда Protocol Buffers не являются хорошим выбором?

Protocol Buffers подходят не для всех данных. В частности:

  • Protocol Buffers склонны предполагать, что все сообщения могут быть загружены в память сразу и не больше графа объектов. Для данных, которые превышают несколько мегабайт, рассмотрите другое решение; при работе с большими данными вы можете эффективно получить несколько копий данных из-за сериализованных копий, что может вызвать неожиданные скачки в использовании памяти.
  • Когда Protocol Buffers сериализуются, одни и те же данные могут иметь много разных бинарных сериализаций. Вы не можете сравнить два сообщения на равенство без их полного разбора.
  • Сообщения не сжаты. Хотя сообщения можно сжимать zip или gzip, как любой другой файл, специализированные алгоритмы сжатия, подобные используемым JPEG и PNG, произведут гораздо меньшие файлы для данных соответствующего типа.
  • Сообщения Protocol Buffers менее чем максимально эффективны как по размеру, так и по скорости для многих научных и инженерных применений, связанных с большими, многомерными массивами чисел с плавающей запятой. Для этих приложений FITS и подобные форматы имеют меньше накладных расходов.
  • Protocol Buffers не очень хорошо поддерживаются в не-объектно-ориентированных языках, популярных в научных вычислениях, таких как Fortran и IDL.
  • Сообщения Protocol Buffers по своей сути не самоописывают свои данные, но они имеют полностью рефлективную схему, которую вы можете использовать для реализации самоописания. То есть вы не можете полностью интерпретировать сообщение без доступа к его соответствующему файлу .proto.
  • Protocol Buffers не являются формальным стандартом какой-либо организации. Это делает их непригодными для использования в средах с юридическими или иными требованиями строить поверх стандартов.

Кто использует Protocol Buffers?

Многие проекты используют Protocol Buffers, включая следующие:

Как работают Protocol Buffers?

На следующей диаграмме показано, как вы используете Protocol Buffers для работы с вашими данными.

Workflow компиляции, показывающий создание proto-файла, сгенерированного кода и скомпилированных классов
Рисунок 1. Workflow Protocol Buffers

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

Следующие примеры кода показывают пример этого потока в Java. Как показано ранее, это определение .proto:

message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;
}

Компиляция этого файла .proto создает класс Builder, который вы можете использовать для создания новых экземпляров, как в следующем Java-коде:

Person john = Person.newBuilder()
    .setId(1234)
    .setName("John Doe")
    .setEmail("jdoe@example.com")
    .build();
output = new FileOutputStream(args[0]);
john.writeTo(output);

Затем вы можете десериализовать данные, используя методы, которые Protocol Buffers создает на других языках, например, C++:

Person john;
fstream input(argv[1], ios::in | ios::binary);
john.ParseFromIstream(&input);
int id = john.id();
std::string name = john.name();
std::string email = john.email();

Синтаксис определений Protocol Buffers

При определении файлов .proto вы можете указать мощность (cardinality) (одиночное (singular) или повторяющееся (repeated)). В proto2 и proto3 вы также можете указать, является ли поле опциональным (optional). В proto3 установка поля как optional изменяет его с неявного присутствия на явное.

После установки мощности поля вы указываете тип данных. Protocol Buffers поддерживают обычные примитивные типы данных, такие как целые числа, логические значения и числа с плавающей запятой. Полный список см. в Скалярные типы значений.

Поле также может быть:

  • Типом message, чтобы вы могли вкладывать части определения, например, для повторяющихся наборов данных.
  • Типом enum, чтобы вы могли указать набор значений для выбора.
  • Типом oneof, который вы можете использовать, когда сообщение имеет много опциональных полей и не более одного поля будет установлено одновременно.
  • Типом map, чтобы добавить пары ключ-значение в ваше определение.

Сообщения могут разрешать расширения (extensions) для определения полей вне самого сообщения. Например, внутренняя схема сообщений библиотеки protobuf позволяет расширения для пользовательских, специфичных для использования опций.

Для получения дополнительной информации о доступных опциях см. руководство по языку для proto2, proto3 или edition 2023.

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

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

После присвоения имени полю вы присваиваете номер поля. Номера полей не могут быть перепрофилированы или повторно использованы. Если вы удаляете поле, вам следует зарезервировать его номер поля, чтобы предотвратить случайное повторное использование номера кем-либо.

Поддержка дополнительных типов данных

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

История

Чтобы прочитать об истории проекта Protocol Buffers, см. История Protocol Buffers.

Философия открытого исходного кода Protocol Buffers

Protocol Buffers были открыты в 2008 году как способ предоставить разработчикам за пределами Google те же преимущества, которые мы получаем от них внутри компании. Мы поддерживаем сообщество открытого исходного кода с помощью регулярных обновлений языка по мере того, как мы вносим эти изменения для поддержки наших внутренних требований. Хотя мы принимаем избранные pull-запросы от внешних разработчиков, мы не всегда можем расставить приоритеты для запросов на функции и исправления ошибок, которые не соответствуют конкретным потребностям Google.

Сообщество разработчиков

Чтобы получать уведомления о предстоящих изменениях в Protocol Buffers и общаться с разработчиками и пользователями protobuf, присоединяйтесь к Группе Google.

Дополнительные ресурсы