Буфер протокола x JSON для сериализации данных

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

Однако есть некоторые проблемы: он не различает integer и float и не указывает точность. Это может стать проблемой, когда вы имеете дело с большими числами. Например, целые числа больше 2ˆ53 не могут быть точно представлены в формате IEEE 754 с плавающей запятой двойной точности, что приводит к неправильному разбору этих чисел. Кроме того, он не поддерживает двоичные строки (последовательность символов без кодировки)[1].

Существуют и другие форматы/инструменты сериализации/десериализации, такие как XML, Apache Thrift, Apache Avro, Protocol Buffers и другие, которые могут быть жизнеспособными альтернативами для различных приложений. Цель этого поста — сфокусироваться на сравнении Protocol Buffer с JSON: производительность в операциях сериализации/десериализации, размер сериализованных данных и способы использования. Тест будет основан на языке Go.

Буфер протокола

Protocol Buffer — это механизм, созданный компанией Google для сериализации структурированных данных, при этом он не зависит от языка и платформы. Он основан на схемах, имеет бинарный формат, обратную/прямую совместимость и генератор кода для нескольких языков, таких как: C++, C#, Dart, Go, Java, Kotlin, Python, Ruby, Objective C, Javascript, PHP и др.

Пример схемы:

message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Числа — это теги полей, которые используются в алгоритме сериализации/десериализации и позволяют схеме развиваться, обеспечивая совместимость со старыми и новыми версиями. После определения схемы, код на выбранном языке (в данном случае Go) генерируется с помощью компилятора, например:

protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/person.proto
Войдите в полноэкранный режим Выход из полноэкранного режима

Для получения более подробной информации об инструменте, его использовании, ссылок и примеров посетите сайт https://developers.google.com/protocol-buffers.

Как проводилось сравнение

Я создал простой API на Go, где данные сохраняются в экземпляре Redis, оба запускаются через Docker. Существуют конечные точки POST (сохранить данные в Redis) и GET (загрузить данные в Redis и вернуть):

Для сравнения использовались те же данные, которые заданы здесь в жесткой кодировке.

Были проведены следующие тесты:

  • Пройдите эталонные тесты, в которых сравнивается количество операций сериализации и десериализации, выполняемых в секунду.
  • Тесты с использованием инструмента WRK для выполнения одновременных запросов на маршрутах и сравнения количества запросов в секунду, поддерживаемых API
  • Оценка размера и формата сериализованных данных в Redis

Все тесты проводились на компьютере macbook со следующими характеристиками:

MacBook Pro
Processador: 2 GHz Quad-Core Intel Core i5
Memória: 16 GB 3733 MHz LPDDR4X
Войдите в полноэкранный режим Выход из полноэкранного режима

Выполнение тестов

Контрольные функции

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

  • Сериализация
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz

BenchmarkSerializeJSON-8                           42920             28149 ns/op
BenchmarkSerializeProtocolBuffer-8                 44228             27277 ns/op
BenchmarkSerializeProtocolBufferAsJSON-8           30870             40263 ns/op
Войдите в полноэкранный режим Выход из полноэкранного режима
  • Де-сериализация
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz

BenchmarkDeserializeJSON-8                        110302             10970 ns/op
BenchmarkDeserializeProtocolBuffer-8              562112              2198 ns/op
BenchmarkDeserializeProtocolBufferAsJSON-8         69072             18385 ns/op
Войдите в полноэкранный режим Выход из полноэкранного режима

Контрольные запросы

ПОСТ

ProtocolBuffer
Running 10s test @ http://localhost:8888/proto
  100 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    36.22ms   24.47ms 249.28ms   71.60%
    Req/Sec    29.12     13.40   161.00     76.51%
  29326 requests in 10.10s, 2.10MB read
Requests/sec:   2903.42
Transfer/sec:    212.65KB
Войдите в полноэкранный режим Выход из полноэкранного режима
JSON
Running 10s test @ http://localhost:8888/json
  100 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    79.52ms  169.71ms   1.09s    93.17%
    Req/Sec    29.16     13.22   110.00     76.06%
  26869 requests in 10.09s, 1.92MB read
Requests/sec:   2661.85
Transfer/sec:    194.96KB
Войдите в полноэкранный режим Выход из полноэкранного режима
ProtocolBuffer как JSON
Running 10s test @ http://localhost:8888/protojson
  100 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    40.33ms   27.15ms 241.85ms   70.52%
    Req/Sec    26.16     12.45   100.00     63.87%
  26330 requests in 10.10s, 1.88MB read
Requests/sec:   2607.63
Transfer/sec:    190.99KB
Войдите в полноэкранный режим Выход из полноэкранного режима

ПОЛУЧИТЬ

ProtocolBuffer
Running 10s test @ http://localhost:8888/proto
  100 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    25.33ms   14.87ms 143.20ms   72.99%
    Req/Sec    40.86     14.23   171.00     74.51%
  41078 requests in 10.10s, 13.59MB read
Requests/sec:   4067.33
Transfer/sec:      1.35MB
Войдите в полноэкранный режим Выход из полноэкранного режима
JSON
Running 10s test @ http://localhost:8888/json
  100 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    26.13ms   15.08ms 135.74ms   71.77%
    Req/Sec    39.44     13.27   171.00     77.17%
  39667 requests in 10.10s, 22.66MB read
Requests/sec:   3927.37
Transfer/sec:      2.24MB
Войдите в полноэкранный режим Выход из полноэкранного режима
ProtocolBuffer как JSON
Running 10s test @ http://localhost:8888/protojson
  100 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    27.08ms   15.91ms 156.36ms   73.76%
    Req/Sec    38.24     13.48   141.00     75.38%
  38499 requests in 10.09s, 22.65MB read
Requests/sec:   3813.99
Transfer/sec:      2.24MB
Войдите в полноэкранный режим Выход из полноэкранного режима

Размер и формат данных, сериализованных в Redis

Размер данных указывается в байтах.

ProtocolBuffer

MEMORY USAGE "address-PROTO"
(integer) 312
Войдите в полноэкранный режим Выход из полноэкранного режима
GET "address-PROTO"
"npnbname 101x12$2ed0bb14-6710-42a8-931f-69d7ae3d0a8ex1atemail 384"nnb15-99-18"x0bna11-7-70x10x01"x0cnb18-64-85x10x02*x0cbxfcxf0xc9x85x06x10xc4xa7xa8x9ex01nqnbname 660x12$25a4b343-98da-42b8-85fa-be3e6ea0e0d2x1atemail 410"nnb20-50-93"x0cnb88-56-96x10x01"x0cnb50-78-43x10x02*x0cbxfcxf0xc9x85x06x10xb8xc7xaax9ex01"
Войдите в полноэкранный режим Выход из полноэкранного режима

JSON

MEMORY USAGE "address-JSON"
(integer) 568
Войдите в полноэкранный режим Выход из полноэкранного режима
GET "address-JSON"
"{"people":[{"name":"name 992","id":"59ed3f8a-ef20-48c6-a392-52663360658c","email":"email 122","phones":[{"number":"19-31-88"},{"number":"80-46-55","type":1},{"number":"33-58-89","type":2}],"last_updated":{"seconds":1622308984,"nanos":121239469}},{"name":"name 553","id":"0287e61b-5f63-45a9-8dff-9ebae231f31b","email":"email 783","phones":[{"number":"82-23-38"},{"number":"91-36-36","type":1},{"number":"62-58-71","type":2}],"last_updated":{"seconds":1622308984,"nanos":121285583}}]}"
Войдите в полноэкранный режим Выход из полноэкранного режима

Буфер протокола в формате JSON

MEMORY USAGE "address-PROTO-JSON"
(integer) 576
Войдите в полноэкранный режим Выход из полноэкранного режима
GET "address-PROTO-JSON"
"{"people":[{"name":"name 284","id":"6bd9b8a1-bf1b-4999-8bb2-11839ed5cc06","email":"email 590","phones":[{"number":"98-63-12"},{"number":"49-36-95","type":"HOME"},{"number":"24-81-62","type":"WORK"}],"lastUpdated":"2021-05-29T17:23:11.963003464Z"},{"name":"name 666","id":"629fad5c-ce7b-4265-9dcc-be99aac9bf63","email":"email 545","phones":[{"number":"50-92-79"},{"number":"18-63-76","type":"HOME"},{"number":"31-33-11","type":"WORK"}],"lastUpdated":"2021-05-29T17:23:11.963026415Z"}]}"
Войдите в полноэкранный режим Выход из полноэкранного режима

Результаты

Контрольные функции

  • Сериализация
Формат Операции нс/оп
ProtocolBuffer 42920 28149
JSON 44228 27277
ProtocolBuffer как JSON 30870 40263
  • Десериализация
Формат Операции нс/оп
ProtocolBuffer 110302 10970
JSON 562112 2198
ProtocolBuffer как JSON 69072 18385

Контрольные запросы

  • ПОСТ
Формат Рек/с
ProtocolBuffer 2903.42
JSON 2661.85
ProtocolBuffer как JSON 2607.63
  • ПОЛУЧИТЬ
Формат Рек/с
ProtocolBuffer 4067.33
JSON 3927.37
ProtocolBuffer как JSON 3813.99

Размер сериализованных данных в Redis

Формат Размер в байтах
ProtocolBuffer 312
JSON 568
ProtocolBuffer как JSON 576

Заключение

После тестирования легко сделать вывод, что использование Protocol Buffer в двоичном формате лучше во всех трех вопросах по сравнению с JSON: он выполняет больше операций в секунду при сериализации (~ +3%) и десериализации (~ +400%), API поддерживает больше запросов в секунду по POST (~ +9%) и GET (~ +3,5%) и размер сериализованных данных меньше (~ -55%). С другой стороны, Protocol Buffer as JSON работает хуже, чем JSON, и его не рекомендуется использовать. Недостатком является то, что поскольку это двоичный формат, сохраненные данные невозможно прочитать. Но я понимаю, что это небольшой недостаток, поскольку манипулировать данными будет программное обеспечение, а не человек.

Впечатляет разница в размере сериализованных данных (-55%). Если рассматривать крупные приложения, которые передают большой объем данных по сети и хранят их в кэшах и базах данных, то легко увидеть большое преимущество, которое дает использование Protocol Buffer: данные будут передаваться быстрее, будет использоваться меньшая пропускная способность сети и меньше места для хранения, что в конечном итоге означает экономию денег.

Кроме того, с инструментами для генерации кода на нескольких языках из определенной schema, интеграция клиентов становится проще, хотя есть опасения по поводу эволюции schema. Еще один инструмент, который интересен с этой точки зрения, — gRPC, но это уже для другого поста. 🙂

Ссылки

  • https://developers.google.com/protocol-buffers
  • https://developers.google.com/protocol-buffers/docs/reference/overview
  • https://github.com/michelaquino/protobuffer-json-comparison
  • [1] Клеппманн, Мартин. Проектирование приложений с интенсивным использованием данных. O’Reilly Media

Оцените статью
devanswers.ru
Добавить комментарий