Перейти к содержимому

Protocol Buffers

Protocol Buffers (protobuf) — бинарный формат сериализации от Google (используется в gRPC, микросервисах). Компактнее JSON в 3–10 раз, парсинг в 5–10 раз быстрее. .proto файл — единый источник истины (описывает сообщения и сервисы), компилятор protoc генерирует код на 10+ языках. Теги полей (1-15 занимают 1 байт) нельзя менять, можно только добавлять новые поля (обратная совместимость). Преимущества: строгая типизация, генерация кода, совместимость. Недостатки: нечитаемый, обязательная схема, не для браузеров (gRPC-Web ограничен). Когда использовать: микросервисы, высокая нагрузка, мобильные приложения, мультиязычные системы. Когда нет: публичные API, человеко-читаемые данные (конфиги, логи), маленькие проекты.

Введение: Когда текста слишком много

Представьте, что вы пересылаете фотографию. Можно отправить её как текстовое описание: “красный квадрат 100x100 пикселей, в центре жёлтый круг”. Это понятно, но очень длинно. А можно отправить бинарный файл — сами байты картинки. Это нечитаемо для человека, но компьютер обработает его за доли секунды.

JSON и XML — как текстовое описание. Человек может прочитать, отредактировать, понять. Но они занимают много места и медленно парсятся.

Protocol Buffers (protobuf) — как бинарный файл. Компактный, быстрый, но нечитаемый. Это формат сериализации, разработанный Google для внутренних нужд. Он используется в gRPC, Kafka, многих микросервисных архитектурах.

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

Зачем нужны Protocol Buffers

Проблемы JSON и XML

ПроблемаJSONXML
РазмерБольшой (текст)Очень большой (теги)
Скорость парсингаСредняяМедленная
ТипизацияСлабаяСлабая (всё строки)
СхемаОпциональна (JSON Schema)Опциональна (XSD)
Генерация кодаНе встроенаНе встроена

Что предлагает protobuf

ПреимуществоОбъяснение
КомпактностьБинарный формат, размер в 3-10 раз меньше JSON
СкоростьПарсинг бинарных данных быстрее текста
Строгая типизация.proto схема обязательна
Генерация кодаКлиенты и серверы на 10+ языках из одного .proto
Обратная совместимостьДобавлять поля можно, не ломая старых клиентов
ПоддержкаGoogle, gRPC, огромная экосистема

Основные концепции

.proto файл

Всё начинается с .proto файла — описания структур данных и сервисов.

syntax = "proto3";

package example.user;

// Сообщение (структура данных)
message User {
    int32 id = 1;           // поле с тегом 1
    string name = 2;        // поле с тегом 2
    string email = 3;       // поле с тегом 3
    int32 age = 4;          // поле с тегом 4
    Address address = 5;    // вложенное сообщение
    repeated string phones = 6;  // массив
}

message Address {
    string city = 1;
    string street = 2;
    int32 zip = 3;
}

// Сервис (для gRPC)
service UserService {
    rpc GetUser (GetUserRequest) returns (User);
    rpc CreateUser (CreateUserRequest) returns (User);
}

message GetUserRequest {
    int32 user_id = 1;
}

message CreateUserRequest {
    string name = 1;
    string email = 2;
    int32 age = 3;
}

Типы данных

ТипОписаниеJSON аналог
stringСтрока UTF-8string
int32, int64Целые числа (со знаком)number
uint32, uint64Беззнаковые целыеnumber
sint32, sint64Целые с ZigZag кодированиемnumber
fixed32, fixed64Фиксированный размер (4/8 байт)number
float, doubleЧисла с плавающей точкойnumber
boolЛогическийboolean
bytesБинарные данныеbase64 строка
enumПеречислениеstring
messageВложенное сообщениеobject
repeatedМассив (список)array
mapСловарьobject

Теги полей (Field tags)

Числа (1, 2, 3) — это теги, не значения. Они используются для идентификации полей в бинарном формате.

message User {
    int32 id = 1;        // тег 1
    string name = 2;     // тег 2
    string email = 3;    // тег 3
}

Правила тегов:

ДиапазонНазначение
1-15Занимают 1 байт. Для частых полей
16-2047Занимают 2 байта. Для редких полей
19000-19999Зарезервировано protobuf
20000-21999Зарезервировано для разработчиков

Важно: Теги нельзя менять. Если вы создали поле с тегом 2, оно останется с тегом 2 навсегда. Удалять поля можно, но тег не переиспользовать.

Правила совместимости

ИзменениеСовместимостьПример
Добавить новое поле✅ Обратно совместимоСтарый код игнорирует новое поле
Удалить поле✅ (если не использовать тег)Старый код не заметит
Изменить имя поляТег остаётся тем же
Изменить тип поля❌ Несовместимоint32 → string
Изменить тег❌ НесовместимоБыло 1, стало 2

Сериализация в protobuf

Как данные превращаются в байты

Protobuf кодирует данные очень компактно.

Пример: сообщение User

message User {
    int32 id = 1;
    string name = 2;
}

Данные: id = 123, name = "Иван"

Кодирование:

ПолеТегТипЗначениеКодированные байты
id1int3212308 7B (2 байта)
name2string“Иван”12 08 D0 98 D0 B2 D0 B0 D0 BD (10 байт)

Итого: 12 байт

Тот же объект в JSON: {"id":123,"name":"Иван"} — 27 байт

Экономия: более чем в 2 раза.

Wire types (как кодируются типы)

ТипWire typeКодирование
int32, int640 (Varint)Переменное количество байт
float, double1 (64/32-bit)Фиксированный размер
string, bytes, message2 (Length-delimited)Длина + данные
sint32, sint640 (Varint с ZigZag)Для отрицательных чисел
fixed32, fixed645/1 (32/64-bit)Фиксированный размер

Varint (Variable-length integer):

  • Маленькие числа (0-127) — 1 байт
  • Большие числа — больше байт
  • Эффективно для ID, счётчиков, порогов

ZigZag (для отрицательных):

  • -1 → 1 (1 байт)
  • 1 → 2 (1 байт)
  • -2 → 3 (1 байт)

Генерация кода

Установка protoc (компилятора protobuf)

# macOS
brew install protobuf

# Linux
apt install protobuf-compiler

# Windows
# Скачать с github.com/protocolbuffers/protobuf/releases

Установка плагинов для языка

# Go
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# Python
pip install grpcio-tools

# Java (gradle plugin)
id 'com.google.protobuf' version '0.9.1'

# Node.js
npm install -g protoc-gen-js

Генерация

# Go
protoc --go_out=. --go-grpc_out=. user.proto

# Python
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. user.proto

# Java
protoc --java_out=. user.proto

Примеры использования

Go

// Сгенерированный код
import pb "example/proto/user"

// Сериализация
user := &pb.User{
    Id:    123,
    Name:  "Иван",
    Email: "ivan@example.com",
}
data, err := proto.Marshal(user)

// Десериализация
var parsed pb.User
err = proto.Unmarshal(data, &parsed)
fmt.Println(parsed.Name) // "Иван"

Python

import user_pb2

# Сериализация
user = user_pb2.User()
user.id = 123
user.name = "Иван"
user.email = "ivan@example.com"
data = user.SerializeToString()

# Десериализация
parsed = user_pb2.User()
parsed.ParseFromString(data)
print(parsed.name)  # "Иван"

Java

// Сгенерированный код
import com.example.UserProto.User;

// Сериализация
User user = User.newBuilder()
    .setId(123)
    .setName("Иван")
    .setEmail("ivan@example.com")
    .build();
byte[] data = user.toByteArray();

// Десериализация
User parsed = User.parseFrom(data);
System.out.println(parsed.getName()); // "Иван"

Protobuf vs JSON vs XML

ХарактеристикаJSONXMLProtobuf
ФорматТекстТекстБинарный
ЧитаемостьОтличнаяСредняяНет
Размер (условно)100%200-300%20-40%
Скорость парсинга1x0.5x5-10x
ТипизацияСлабаяСлабаяСтрогая
СхемаОпциональноXSD (опционально)Обязательна (.proto)
Генерация кодаНет (инструменты есть)НетДа (встроена)
Поддержка языковВсеВсе10+
СжатиеНет (можно gzip)Нет (можно gzip)Встроенное
Потоковая обработкаСложноСложно (SAX)Да

Когда использовать protobuf

Идеальные сценарии

СценарийПочему protobuf
МикросервисыgRPC + protobuf = высокая производительность
Высокая нагрузкаЭкономия трафика, быстрый парсинг
Мобильные приложенияМеньше трафика, меньше батареи
Хранение данныхКомпактные файлы, быстрая загрузка
Межсерверное взаимодействиеБинарный протокол, строгие контракты
Стриминг данныхПотоковая сериализация
Мультиязычные системыГенерация кода из .proto

Плохие сценарии

СценарийПочему не protobuf
Публичное APIВнешние разработчики не любят бинарные форматы
Человеко-читаемые данныеКонфиги, логи, отладка
БраузерыНет нативной поддержки (нужен gRPC-Web)
Очень маленькие проектыОверхед на .proto и генерацию
Динамические данныеСхема фиксирована

Расширения и экосистема

gRPC

Protobuf + gRPC = идеальная пара для микросервисов.

service UserService {
    rpc GetUser (GetUserRequest) returns (User);
    rpc ListUsers (ListUsersRequest) returns (stream User);  // server streaming
    rpc CreateUsers (stream User) returns (Summary);        // client streaming
}

gRPC-Gateway

Генерирует REST API из .proto файлов.

protoc --grpc-gateway_out=. user.proto

Результат: Один .proto → gRPC сервер + REST прокси.

protoc-gen-doc

Генерирует документацию из .proto.

protoc --doc_out=. --doc_opt=markdown,user.md user.proto

Buf (инструмент для управления protobuf)

# Линтер, форматтер, разбивка на модули
buf lint
buf format
buf generate

Продвинутые возможности

Oneof (одно из полей)

message Response {
    oneof result {
        User user = 1;
        Error error = 2;
    }
}

В сообщении будет либо user, либо error, но не оба.

Any (любой тип)

import "google/protobuf/any.proto";

message Event {
    string type = 1;
    google.protobuf.Any payload = 2;
}

Timestamp (стандартный тип для времени)

import "google/protobuf/timestamp.proto";

message Order {
    int32 id = 1;
    google.protobuf.Timestamp created_at = 2;
}

Map (словарь)

message Config {
    map<string, string> settings = 1;
}

Опции полей

message User {
    int32 id = 1 [(deprecated) = true];
    string name = 2 [(my_option).description = "имя пользователя"];
}

Версионирование .proto

Стратегия 1: Новые поля (обратная совместимость)

// v1
message User {
    int32 id = 1;
    string name = 2;
}

// v2 (совместимо)
message User {
    int32 id = 1;
    string name = 2;
    string email = 3;      // новое поле
    string phone = 4;      // новое поле
}

Стратегия 2: Разные пакеты

// v1/user.proto
package user.v1;

// v2/user.proto
package user.v2;

Стратегия 3: Reserved для удалённых полей

message User {
    reserved 2, 5 to 7;      // зарезервированные теги
    reserved "old_field";    // зарезервированное имя
    int32 id = 1;
    string name = 3;
}

Распространённые ошибки

Ошибка 1: Менять теги

// Было
message User {
    int32 id = 1;
    string name = 2;
}

// Стало (несовместимо!)
message User {
    int32 user_id = 2;   // тег изменился
    string name = 1;     // тег изменился
}

Исправление: Не менять теги. Добавлять новые поля с новыми тегами.

Ошибка 2: Использовать protobuf для больших файлов

Protobuf не предназначен для сообщений > 100 МБ. Парсинг всего сообщения в память может быть проблемой.

Исправление: Для больших файлов используйте потоковую обработку или отдельный сервис (S3).

Ошибка 3: Не указывать package

// Плохо
message User {}

// Хорошо
package com.example.user.v1;
message User {}

Ошибка 4: Игнорировать совместимость

Удалили поле и переиспользовали тег для нового поля.

Исправление: Использовать reserved для удалённых полей.

Ошибка 5: Protobuf для публичного API

Сделали публичное API на protobuf. Внешние разработчики не могут использовать его из браузера.

Исправление: REST + JSON для публичных API. Protobuf для внутренних.

Резюме для системного аналитика

  1. Protocol Buffers (protobuf) — бинарный формат сериализации от Google. Компактный, быстрый, со строгой схемой. Используется в gRPC, микросервисах, высоконагруженных системах.

  2. Основные преимущества: размер в 3-10 раз меньше JSON, парсинг в 5-10 раз быстрее, строгая типизация, генерация кода на 10+ языках, обратная совместимость.

  3. .proto файл — единый источник истины. Описывает сообщения и сервисы. Компилятор protoc генерирует код для нужного языка.

  4. Теги полей — числа, идентифицирующие поля в бинарном формате. Теги 1-15 занимают 1 байт (для частых полей). Теги нельзя менять, можно только добавлять новые.

  5. Совместимость: добавлять поля — можно, удалять — можно (но тег зарезервировать), менять тип или тег — нельзя.

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

  7. Когда НЕ использовать: публичные API, человеко-читаемые данные, браузеры, маленькие проекты.

Проверка знаний

Вопрос 1 из 4
Что лучше всего описывает Protocol Buffers?
Какое преимущество protobuf особенно выделяется по сравнению с JSON и XML?
Для чего в protobuf нужны теги полей, например `name = 2`?
Какое правило совместимости protobuf указано как важное?

Вопросы, где были ошибки