Эффективное взаимодействие — протокол GRPC и язык protobuf (Ростислав Шаниязов, OSEDUCONF-2022)
- Докладчик
- Ростислав Шаниязов
В современной микросервисной архитектуре остро стоит вопрос об описании жёсткого контракта взаимодействия между сервисами.
Язык protobuf позволяет описывать сущности, а также существуют множество расширений для контроля code style, генерации документации, генерации контроллеров и клиентов для протокола grpc, а также контроль обратной совместимости.
В этой работе мы подробнее расскажем о его возможностях и покажем конкретные примеры на protobuf и протокола grpc.
Содержание
Видео
Презентация
Thesis
Протокол GRPC является детищем кампании Google и довольно популярен в последнее время. Было много статей о том, как работает протокол, в чём плюсы и минусы. В данной статье мы рассмотрим опыт взаимодействия с данным протоколом, рассмотрим нюансы его использования и поговорим, как данный инструмент позволяет нам поддерживать чистый API.
GRPC хорошо интегрирован со средой разработки и его легко использовать.
Зачем?
В парадигме разработки микросервисов первый шаг начинается с API (API First)[1].
С точки зрения CI/CD, микросервисы представляются для нас в виде чёрной коробочки, которая выполняет некоторый контракт. С другой стороны, не хочется ограничивать сам выбор инструмента разработки будь то сервер на java, сервер на Go, либо сервер на NodeJS.
При этом мы хотим иметь инструменты для написания чистого кода: проверка синтаксиса, использование линтера для проверки кода, контроль внесённых изменений: должна присутствовать проверка обратной совместимости новой версии API по сравнению с предыдущей версией, генерация отчёта, который будет в человекочитаемом формате.
Язык Protobuf
Protobuf спроектирован таким образом, что всегда инициализирует примитивы самостоятельно (даже строка инициализируется по умолчанию "") и объекты, если примитивы необязательны, используйте объекты, рекомендуем использовать классы, предоставленные Google Wrappers, так как в них есть вспомогательные методы для работы с примитивами.
Пример описания:
// Это описание простого Message message Message{ // Тут примитив в виде id-ка в виде 64-битный целочисленного числа // По умолчанию 0 int64 id = 1; // Объект в виде обёртки 64-битный целочисленного числа // Этот объект не обязательно инициализировать google.protobuf.Int64Value long = 2; // Какая-то обязательная строка string some_string = 3; // Вот тут объект строка, она может быть не инициализирована, // если поле необязательно google.protobuf.StringValue string = 4; // Передаём таймстемп google.protobuf.Timestamp date = 5; // ... }
Структура описания поля в сообщении состоит в следующем: [тип сообщения] {название поля = (номер поля);.
Для корректной генерации кода рекомендуется следовать style guide[2]
Линтер proto
Линтер proto файлов позволяет нам контролировать стиль описания кода (camel case, структура enum, обязательные опции для proto и прочее). Данный инструмент позволяет облегчить сам процесс ревью API и автоматизировать процесс отлова глупых опечаток и ошибок в нём.
Реализуется с помощью плагина protolint[3]. Распространяется в виде исполняемого файла, который написан на Go.
protolint lint -output_file=protolint_results.txt ваш_файл.proto
Результат выполнения команды будет записан в protolint_results.txt. Если нет ошибок, то этот файл пуст.
Проверка обратной совместимости
Довольно часто в Rest API возникала такая ситуация: было убрано поле в объекте, связь сервер-клиент сломалась. Многие компании решают данную проблему путём жёсткого ограничения лиц, которые имеют доступ к редактированию API и жёсткого палочного подхода, либо раздутие моделек до невообразимого масштаба.
Proto позволяет нам редактировать модельку сообщения таким образом, чтобы связь сервер-клиент не развалилась. Для того чтобы изменить модельку с сохранением обратной совместимости, нужно использовать ключевое слово reserved. Резервировать нужно как название поля, так и его номер. Пример:
// Это описание простого Message message Message{ reserved 2; reserved "long"; // Тут примитив в виде id-ка в виде 64-битный целочисленного числа // По умолчанию 0 int64 id = 1; // Какая-то обязательная строка string some_string = 3; // Вот тут объект строка, она может быть не инициализирована, чтобы её не google.protobuf.StringValue string = 4; // Передаём таймстемп google.protobuf.Timestamp date = 5; // ... }
Проверять автоматически, сохранилась ли обратная совместимость, можно с помощью плагина protolock.
Также распространяется в виде исполняемого файла, написанного на Go.
protolock status
Программа анализирует все вложенные proto файлы и на основе своих внутренних правил, соответствующие спецификации gRPC. В случае какого-то несовместимого изменения это отобразится в консоль и консоли будет выполнена команда прерывания.
Для того чтобы записать изменения, нужно выполнить команду: protolock commit
Генерация кода и документации
Для генерации кода следует установить golang. И в самом коде установить библиотеку:
go get github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc
Потом можно сгенерировать документацию на основе proto файла:
protoc -I. -Ithird_party --doc_out=./library-grpc-api-docs/ --doc_opt=markdown,file_output.md folder
Похожие действия применимы к генерации swagger-кода на
Java/Go/Python/C …
Особенности GRPC
Протокол использует длинные http 2.0 запросы. Соединение открывается и держится около 100 лет, что теоретически позволяет нам сэкономить время на открытие и закрытие сокета. Следуя из этого, можно выделить следующее:
- Бывают случаи, когда клиент открыл соединение и «умер», не закрыв его. Сервер тратит вычислительные ресурсы на соединение и ждёт, когда клиент либо закроет соединение, либо когда истечёт время соединения (около 100 лет). Для этого нужно принудительно перезагружать сервер. Также от этого спасает Istio в виде Service-Mesh.
- Если вы хотите правильно балансировать нагрузку между клиентами и серверами, нужно использовать внешний балансировщик, иначе можно получить следующую ситуацию: 2 клиента открывают соединение по http к одному серверу и держат его. В итоге получается, что вторая реплика сервера не задействована. Это показано на рисунке.