Learning Platform
Глоссарий Troubleshooting
Урок 06.03 · 30 мин
Средний
ThriftTBinaryProtocolTCompactProtocolTransport LayerDelta EncodingWire Format

Apache Thrift: Protocols

Обзор Thrift

Apache Thrift — фреймворк для кросс-языковой сериализации и RPC, созданный в Facebook (2007), Apache с 2010. Текущая версия — 0.22.0 (May 2025). Ключевое отличие от Protobuf: Thrift — полный RPC-фреймворк со встроенным transport layer, protocol layer и server runtime. Protobuf — только формат сериализации (gRPC — отдельный проект).

Поддерживает 28 языков: C++, Java, Python, Go, Rust, C#, JavaScript, Ruby, PHP, Erlang, Haskell, Perl, Dart, и другие.

Thrift Architecture Stack
Application CodeПользовательский код: вызывает сгенерированные методы клиента/сервера. IDL → thrift compiler → client stubs + server handlers.
Generated Code (Processor)Thrift compiler генерирует: client stubs (отправка запросов), processor (маршрутизация вызовов к handler), struct read/write (де/сериализация). IDL → code.
Protocol LayerОпределяет wire format: как типы кодируются в байты. TBinaryProtocol (простой, быстрый), TCompactProtocol (компактный, varint+zigzag), TJSONProtocol (human-readable).
Transport LayerОпределяет как байты доставляются: TCP socket, HTTP, memory buffer, file. TSocket (TCP), THttpTransport (HTTP), TMemoryBuffer (in-memory), TFramedTransport (length-prefixed frames).
Server RuntimeМодель обработки запросов: TSimpleServer (один поток), TThreadPoolServer (thread pool), TNonblockingServer (NIO). Thrift предоставляет ready-to-use сервера.
NOTE

В отличие от Protobuf (где gRPC — отдельный проект на HTTP/2), Thrift включает transport и server runtime в коробке. Это упрощает начальную настройку, но ограничивает гибкость: привязка к Thrift-specific транспортам вместо стандартных HTTP/2 load balancers.

Thrift IDL

Thrift использует собственный IDL (Interface Definition Language):

namespace java com.example.users
namespace py example.users

struct User {
 1: required i32 id,
 2: required string name,
 3: optional string email,
 4: optional i32 age = 25, // explicit default
}

enum Status {
 ACTIVE = 0,
 INACTIVE = 1,
 DELETED = 2,
}

service UserService {
 User getUser(1: i32 id),
 list<User> listUsers(),
 void deleteUser(1: i32 id) throws (1: NotFoundException ex),
}
Thrift IDL vs Protobuf IDL
Thrift IDLThrift IDL: struct (message), service (RPC), enum, typedef, exception, union, const. namespace per language. required/optional explicit. Service = full RPC definition.
Protobuf IDLProtobuf IDL: message, service, enum, oneof, map. package + option for language. Все поля implicit в proto3. Service для gRPC — отдельная генерация.

TBinaryProtocol

Простейший wire format Thrift — fixed-size headers, без компрессии:

TBinaryProtocol: Field Encoding

Field = Type (1B) + Field ID (2B) + Value

Каждое поле в TBinaryProtocol: type byte (1 байт) + field ID (2 байта big-endian) + value. Fixed-size header = 3 байта per field.
Type Byte1 байт: тип данных. BOOL=2, BYTE=3, I16=6, I32=8, I64=10, DOUBLE=4, STRING=11, STRUCT=12, MAP=13, SET=14, LIST=15. Фиксированный набор.
Field ID2 байта big-endian: номер поля из IDL (аналог field number в Protobuf). Позволяет пропускать неизвестные поля.
ValueФормат зависит от type byte. i32: 4 байта big-endian. i64: 8 байта. string: 4-byte length + UTF-8 data. bool: 1 байт.

Stop Byte: 0x00 (конец struct)

Struct завершается stop byte = 0x00. Ридер читает поля пока не встретит type byte = 0. Порядок полей не гарантирован (как в Protobuf).

Пример: User(id=150, name="Al"):

TBinaryProtocol: User(id=150, name='Al')
Field: id (field 1, i32)Type byte: 0x08 (I32). Field ID: 0x00 0x01 (field 1, big-endian). Value: 0x00 0x00 0x00 0x96 (150, big-endian 4 bytes). Итого: 7 байт.
Field: name (field 2, string)Type byte: 0x0B (STRING). Field ID: 0x00 0x02 (field 2). Length: 0x00 0x00 0x00 0x02 (2 bytes, big-endian 4 bytes). Data: 0x41 0x6C ('Al'). Итого: 9 байт.

00 — stop byte. Итого: 17 байт

Stop byte: 0x00. Общий размер: 7 + 9 + 1 = 17 байт. Для сравнения: Protobuf кодирует тот же User(id=150, name='Al') в 7 байт — в 2.4 раза компактнее.
WARNING

TBinaryProtocol не компактный: integers всегда 4/8 байт (fixed-size big-endian), field header — 3 байта. Тот же User(id=150, name="Al") занимает 17 байт vs 7 байт в Protobuf. Для высоконагруженных систем используйте TCompactProtocol.

TCompactProtocol

Компактный формат, использующий varint + zigzag + delta encoding для field IDs:

TCompactProtocol: оптимизации
АспектЧто оптимизируется.
TBinaryProtocolФормат в TBinaryProtocol.
TCompactProtocolФормат в TCompactProtocol.
Field HeaderField header: type + field ID.
3 байта: type(1B) + field_id(2B). Фиксированный размер.
1 байт если delta ≤ 15: (delta << 4) | type. 2-3 байта если delta > 15.
i32i32 кодирование.
Всегда 4 байта big-endian. 150 = 00 00 00 96 (4 байта).
Zigzag + varint. 150 = zigzag(150) = 300 → varint 0xAC 0x02 (2 байта).
i64i64 кодирование.
Всегда 8 байт big-endian.
Zigzag + varint. 1-10 байт зависит от значения.
String lenString length prefix.
4 байта big-endian для length.
Varint для length. Короткие строки: 1 байт.

Delta Encoding для Field IDs

Ключевая оптимизация TCompactProtocol — delta encoding для field IDs. Вместо абсолютного номера поля кодируется разница с предыдущим:

Delta Encoding: field header в 1 байт
Fields: id=1, name=2, email=3Struct с полями: field 1, field 2, field 3. Последовательные номера — идеальный случай для delta encoding.
Field 1 (delta=1)Delta = 1 - 0 = 1 (≤ 15). Один байт: (delta << 4) | compact_type. Для i32: compact_type=5. Байт: (1 << 4) | 5 = 0x15. Предыдущий field ID = 0 (начало struct).
Field 2 (delta=1)Delta = 2 - 1 = 1 (≤ 15). Один байт: (1 << 4) | 8 = 0x18. compact_type для string = 8.
Field 3 (delta=1)Delta = 3 - 2 = 1 (≤ 15). Один байт: (1 << 4) | 8 = 0x18. Тот же байт что и field 2 (string с delta 1).

Если delta > 15 или отрицательная (поля не в порядке): fallback на type byte + zigzag varint field ID.

TCompactProtocol: User(id=150, name='Al')
Field: id (field 1, i32)Delta header: delta=1, compact type=5 (i32) → (1<<4)|5 = 0x15. Value: zigzag(150)=300 → varint 0xAC 0x02. Итого: 3 байта.
Field: name (field 2, string)Delta header: delta=1, compact type=8 (string) → (1<<4)|8 = 0x18. Length: varint(2) = 0x02. Data: 'Al'. Итого: 4 байта.

00 — stop byte. Итого: 8 байт

Stop byte: 0x00. Общий размер: 3 + 4 + 1 = 8 байт. Сравнение: TBinary = 17 байт, Protobuf = 7 байт. TCompact близок к Protobuf.

Stop Byte

Каждый struct (и nested struct) завершается stop byte 0x00. Ридер читает поля в цикле: прочитать header → если type = 0 → конец struct.

Stop Byte и вложенные struct
Outer Struct StartOuter struct начинается. Ридер читает field headers один за другим.
Field 1: id (i32)Обычное скалярное поле. Header + value.
Field 2: address (struct)Вложенный struct. После header — рекурсивное чтение полей inner struct до его stop byte.
Field 3: name (string)Продолжение outer struct после inner stop byte.
0x00 — Outer Stop ByteStop byte outer struct: 0x00. Каждый уровень вложенности имеет свой stop byte.

Transport Layer

Thrift отделяет сериализацию (protocol) от доставки (transport):

Thrift Transports
TSocketПростой TCP socket. Blocking I/O. Подходит для TSimpleServer (один клиент) и TThreadPoolServer (thread per connection).
TFramedTransportLength-prefixed frames: 4-byte big-endian length + payload. Обязателен для TNonblockingServer (NIO). Позволяет читать полное сообщение за один read.
THttpTransportHTTP POST transport. Payload в body. Проходит через HTTP proxies и load balancers. Совместим с REST инфраструктурой.
TMemoryBufferIn-memory byte buffer. Для тестирования и in-process сериализации. Без сетевого overhead.
TIP

TFramedTransport — ключевой транспорт для production: TNonblockingServer требует framed transport, чтобы определить границы сообщений в non-blocking I/O. Если клиент использует TFramedTransport, сервер тоже обязан. Несовпадение → corrupted data.

Thrift vs Protobuf: Wire Size

Сравним на одних данных — User(id=150, name="Al"):

Wire Size: TBinary vs TCompact vs Protobuf
КомпонентЧасть сообщения.
TBinaryTBinaryProtocol: fixed-size headers.
TCompactTCompactProtocol: varint + delta.
ProtobufProtocol Buffers: varint field tags.
id headerField header для id (int32, field 1).
type(1B) + field_id(2B) = 3 байта.
delta header: 1 байт.
field tag: 1 varint = 1 байт.
id valueValue: 150 (int32).
4 байта big-endian.
zigzag(150) = varint(300) = 2 байта.
varint(150) = 2 байта.
name headerField header для name (string, field 2).
type(1B) + field_id(2B) = 3 байта.
delta header: 1 байт.
field tag: 1 varint = 1 байт.
name valueValue: 'Al' (string).
length(4B) + 'Al'(2B) = 6 байт.
length(1B varint) + 'Al'(2B) = 3 байта.
length(1B varint) + 'Al'(2B) = 3 байта.
stop/totalStop byte / итого.
stop byte: 1 байт. Итого: 17 байт.
stop byte: 1 байт. Итого: 8 байт.
Нет stop byte. Итого: 7 байт.

TCompactProtocol почти равен Protobuf по компактности. Разница — 1 байт stop byte. При больших сообщениях overhead стремится к нулю.

Schema Evolution в Thrift

Thrift поддерживает эволюцию схемы аналогично Protobuf: field IDs на wire позволяют пропускать неизвестные поля.

Thrift Schema Evolution
Добавление поляНовый field ID + optional (или с default). Старые клиенты пропускают неизвестный field ID по type byte. Новые клиенты используют default если поле отсутствует.
Удаление поляПрекратить отправку. Нет аналога reserved — ответственность разработчика не переиспользовать field ID. Старые клиенты: поле отсутствует → default.
Thrift requiredThrift до сих пор поддерживает required — и это те же проблемы что в proto2. Required поле нельзя удалить без координированного обновления. Recommendation: не использовать required.
Нет reserved keywordThrift не имеет reserved keyword. Защита от переиспользования field IDs — только дисциплина команды и code review. Это слабее чем Protobuf reserved.

Thrift в современной экосистеме

Где Thrift используется сегодня
Активное использованиеКрупные компании со legacy Thrift инфраструктурой. Meta (Facebook) — основной пользователь, fbthrift. Apache ecosystem: HBase, Cassandra, Hive используют Thrift RPC.
Тренд: миграция на gRPCНовые проекты выбирают gRPC (Protobuf + HTTP/2). Причины: лучший tooling, HTTP/2 load balancing, streaming, interceptors, grpc-web для браузеров. Thrift — legacy choice.
NOTE

Meta поддерживает собственный форк — fbthrift — с улучшениями: Rocket (async server framework), streaming RPC, и интеграция с Folly (C++ library). Apache Thrift 0.22.0 и fbthrift — это разные, несовместимые реализации.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Thrift Architecture Stack: Application → Generated Code → Protocol → Transport → Server. Как это отличается от Protobuf + gRPC?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5