Как клиенты на разных языках используют протокол gRPC
Что проверяют
Понимаете ли вы, что общий язык у клиентов задаёт не Go и не библиотека, а контракт в .proto и формат передачи. Следом спрашивают, как менять контракт, не ломая старых клиентов, и что делать с браузером.
Как отвечать
Каркас из четырёх частей:
- контракт: сервис и сообщения описаны в
.proto. Из него каждый язык генерирует свой код: для Go этоprotoc-gen-goиprotoc-gen-go-grpc, для Pythongrpcio-tools, для Java свой плагин. Удобно собирать всё черезbuf, он же проверяет обратную совместимость; - транспорт: HTTP/2 и бинарная сериализация protobuf. На проводе поле определяется номером, а не именем, поэтому клиенту на Python всё равно, как поле называется в Go-структуре;
- типы вызовов: унарный, серверный поток, клиентский поток и двунаправленный. Все четыре есть в основных реализациях;
- общие соглашения: метаданные (аналог заголовков) для токенов и trace-id, дедлайн, который уходит серверу в заголовке
grpc-timeout, и стандартные коды статуса вродеNotFound,DeadlineExceeded,Unavailable. В Go ошибку отдают черезstatus.Error(codes.NotFound, ...), и клиент на любом языке получает тот же код.
Про эволюцию контракта: номера полей не меняют и не переиспользуют, удалённые помечают reserved, новые поля добавляют с новыми номерами. Старый клиент неизвестные поля просто пропустит.
Браузер напрямую gRPC не говорит, нужен gRPC-Web через прокси или протокол Connect. Для внешних REST-клиентов часто ставят grpc-gateway, который делает JSON-обёртку по тем же .proto.
Где спотыкаются
Меняют номер существующего поля или тип на несовместимый. Старый клиент либо прочитает значение неверно, либо не увидит его вовсе, а ломаются как раз те клиенты, которые никто не пересобрал.
Не ставят дедлайн. В Go вызов с context.Background() без таймаута будет ждать бесконечно, если сервер завис.
Балансируют gRPC обычным L4-балансировщиком. HTTP/2 держит одно долгое соединение, и весь поток клиента уходит на один экземпляр сервера. Нужен L7-прокси или клиентская балансировка.