# gRPC Must Know
В эпоху микросервисов часто у разработчиков возникает желание обращаться к внешним сервисам так, как если бы они были локальными компонентами, а
клиентский код, просто вызывал бы процедуры из этих компонентов.
**gRPC** - один и самых популярных RPC фреймворков/библиотек, которая позволяет максимально абстрагироваться от того факта, что мы вызваем процедуру удаленно - он избавляет нас от рутины:
* подготавливать (писать свои) специальные стурктуры данных (модели/DTO);
* сериализовывать/десериалзовывать их при каждой отправке;
* он даже берет на себя многие рутинные аспекты по работе с сетью (поддержание сетевого соединения, retry под капотом, мультиплексирование и др);
Тем не менее, абстрагироваться полностью о того факта, что общение происходит через сеть не удается: абстракции рано или поздно текут [[1]](#The-Law-Of-Leaky-Abstractions), поэтому, работая с gRPC в частности, мы должны быть готовы к этому.
Более того, возникают ситуации, когда надо решать непростые технические задачи (балансировка, конкурентное общение через стримы и тд), которые неизбежно возникают при высоких нагрузках и которые форсируют нас опуститься на более низкий уровень - на уровень сетевого взаимодействия.
Вспомним основной flow на стороне клиента (вызывающей стороне) в коде при вызове какой-либо RPC или создании стрима с нуля:
1. Создаем gRPC соединение (канал) до целевого сервера;
2. Инициализируем gRPC клиента, которому передаем соединение из предыдущего шага;
3. Вызываем процедуру и получаем ответ или создаем стрим и начинаем с ним работать;
На стороне сервера все проще - надо просто объявить функции-обработчики запросов/стримов.
Уже здесь возникают определенные вопросы:
* Сколько нужно или можно создавать соединений?
* Можно ли на каждую RPC создавать по клиенту?
* Если соединение испортилось (сервер стал недоступен на короткий период времени), то как быть с клиентом и соединением?
Давайте разберем основные концепции RPC с точки зрения практикующего разработчика:
* **gRPC клиент** (*в коде*) - это просто структура данных (объект), который занимается (де)сереализацией и записью/чтением данных в grpc канал (соединение), на этом все!
* **gRPC стрим** (*в коде*) - это просто структура данных (объект), который по сути является узкоспециализированным клиентом: умеет писать и читать только определенные структуры данных и имеет ограниченный набор каноничных методов (как правило, всех интересуют только `Send()`, `Recv()` и `SendClose()`).
* **gRPC канал/соединение** (*в рантайме*) - это структура данных + фоновый процесс, обслуживающий ее, который отвечает за управление настоящим сетевым (*HTTP2*) соединением: инициализация, реконнект, ретраи, keepalive и пр, в него могут конкурентно писать множество gRPC клиентов (это называется мультиплексирование).
Исходя только из этого уже можно дать следующие ответы-тезисы на предыдущие вопросы:
* Соединений (каналов) не должно быть много, так как на каждое из них нужны ощутимые накладные расходы в рантайме и в системе в целом;
* Нет правила "один gRPC канал (соединение) на один gRPC клиент", более того, рекомендуется шейрить 1 gRPC канал между множеством gRPC клиентов (мультиплексирование);
* Клиентов можно создавать много, хоть на каждый вызов RPC по клиенту - они "дешевые", но в силу простоты обычно они создаются примерно в том же количестве, что и gRPC содеинения/каналы;
* Если испортилось gRPC соединение, то клиент может об этом даже не узнать, так как оно может быть восстановлено обслуживающим библиотечным кодом. Но даже если клиент об этом узнал (получил ошибку с кодом `UNAVAILABLE` - отработали все retry политики), то все равно ничего делать с самим соединением, так и с gRPC клиентом **не нужно**: на данный момент нет таких внутренних ошибок из-за которых соединение переходило бы в состояние из которого gRPC фреймворк не смог бы его автоматически восстановить [[2]](#gRPC-Connectivity-Semantics).
Единственный случай, когда оно в такое состояние попадает - это когда клиентский код умышленно вызвает `Close()`, переводя его (соединение) в состояние `SHUTDOWN`;
Исходя из того, что работа с connectivity у gRPC всегда идет на фоне (после содания grpc соединения/канала) можно сделать следующие выводы:
* reconnect реализовывать **не** нужно (но можно настроить). Но важнл, что retry реализация остается на стороне разработчика (т.к. регенте ретраить не удавшийся запрос или нет, обычно принимается исходя из логики более высокого уровня, чем сетевое взаимодействие);
* создание gRPC клиента в коде, как и стрима из клиента никогда не вернет ошибку, связанную с сетевыми проблемами: возвращаются только ошибки мисконфигурации (некорретные TLS сетификаты, формат домена или порта и тп);
* при создании gRPC соединения/канала **не** будет происходить блокировки потока управления, и по умолчанию **не** будет проверки на доступность серевера;
### Полезные материалы
#### The Law Of Leaky Abstractions
[1] [https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/](https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/)
#### gRPC Connectivity Semantics
[2] [https://grpc.github.io/grpc/core/md_doc_connectivity-semantics-and-api.html](https://grpc.github.io/grpc/core/md_doc_connectivity-semantics-and-api.html)