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
| Проблема | JSON | XML |
|---|---|---|
| Размер | Большой (текст) | Очень большой (теги) |
| Скорость парсинга | Средняя | Медленная |
| Типизация | Слабая | Слабая (всё строки) |
| Схема | Опциональна (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-8 | string |
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 = "Иван"
Кодирование:
| Поле | Тег | Тип | Значение | Кодированные байты |
|---|---|---|---|---|
| id | 1 | int32 | 123 | 08 7B (2 байта) |
| name | 2 | string | “Иван” | 12 08 D0 98 D0 B2 D0 B0 D0 BD (10 байт) |
Итого: 12 байт
Тот же объект в JSON: {"id":123,"name":"Иван"} — 27 байт
Экономия: более чем в 2 раза.
Wire types (как кодируются типы)
| Тип | Wire type | Кодирование |
|---|---|---|
int32, int64 | 0 (Varint) | Переменное количество байт |
float, double | 1 (64/32-bit) | Фиксированный размер |
string, bytes, message | 2 (Length-delimited) | Длина + данные |
sint32, sint64 | 0 (Varint с ZigZag) | Для отрицательных чисел |
fixed32, fixed64 | 5/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
| Характеристика | JSON | XML | Protobuf |
|---|---|---|---|
| Формат | Текст | Текст | Бинарный |
| Читаемость | Отличная | Средняя | Нет |
| Размер (условно) | 100% | 200-300% | 20-40% |
| Скорость парсинга | 1x | 0.5x | 5-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.protoBuf (инструмент для управления 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 для внутренних.
Резюме для системного аналитика
Protocol Buffers (protobuf) — бинарный формат сериализации от Google. Компактный, быстрый, со строгой схемой. Используется в gRPC, микросервисах, высоконагруженных системах.
Основные преимущества: размер в 3-10 раз меньше JSON, парсинг в 5-10 раз быстрее, строгая типизация, генерация кода на 10+ языках, обратная совместимость.
.proto файл — единый источник истины. Описывает сообщения и сервисы. Компилятор protoc генерирует код для нужного языка.
Теги полей — числа, идентифицирующие поля в бинарном формате. Теги 1-15 занимают 1 байт (для частых полей). Теги нельзя менять, можно только добавлять новые.
Совместимость: добавлять поля — можно, удалять — можно (но тег зарезервировать), менять тип или тег — нельзя.
Когда использовать: микросервисы, gRPC, высокая нагрузка, мобильные приложения, мультиязычные системы, стриминг.
Когда НЕ использовать: публичные API, человеко-читаемые данные, браузеры, маленькие проекты.