Создание простой службы gRPC на стороне клиента
16 марта 2023 г.Большинство компонентов межсистемной связи, использующих REST, сериализуют свою полезную нагрузку в JSON. На данный момент в JSON отсутствует широко используемый стандарт проверки схемы: схема JSON не получила широкого распространения. Стандартная проверка схемы позволяет делегировать проверку сторонней библиотеке и выполнять ее. Без него мы должны вернуться к ручной проверке кода. Хуже того, мы должны синхронизировать код проверки со схемой.
XML имеет готовую проверку схемы: XML-документ может объявлять грамматику, которой он должен соответствовать. SOAP, основанный на XML, также выигрывает от этого.
Другие варианты сериализации имеют возможность проверки схемы: например,, Avro, Kryo и буферы протокола. Интересно, что gRPC использует Protobuf для предоставления RPC через распределенные компоненты:
<цитата>gRPC — это современная высокопроизводительная платформа удаленного вызова процедур (RPC) с открытым исходным кодом, которая может работать в любой среде. Он может эффективно подключать службы в центрах обработки данных и между ними с подключаемой поддержкой балансировки нагрузки, трассировки, проверки работоспособности и аутентификации. Он также применим на последней миле распределенных вычислений для подключения устройств, мобильных приложений и браузеров к серверным службам.
-- Почему gRPC?
Кроме того, протокол представляет собой механизм бинарной сериализации, который значительно экономит пропускную способность. Таким образом, gRPC — отличный вариант для межсистемного взаимодействия. Но если все ваши компоненты говорят на gRPC, как их могут вызвать простые клиенты? В этом посте мы создадим службу gRPC и покажем, как вызывать ее из curl.
Простой сервис gRPC
Документация gRPC является исчерпывающей, поэтому вот краткое изложение:
* gRPC — это инфраструктура удаленного вызова процедур. * Он работает с широким спектром языков * Он зависит от буферов протокола:
<цитата>Протокольные буферы – это независимый от языка и платформы расширяемый механизм Google для сериализации структурированных данных. Представьте себе XML, но меньше, быстрее и проще. Вы один раз определяете, как должны быть структурированы ваши данные, а затем можете использовать специально сгенерированный исходный код, чтобы легко записывать и считывать структурированные данные из различных потоков данных и с использованием различных языков.
* Это часть портфолио CNCF и в настоящее время находится на стадии инкубации
Давайте настроим нашу службу gRPC. Мы будем использовать Java, Kotlin, Spring Boot и специальный проект интеграции gRPC Spring Boot. Структура проекта содержит два проекта: один для модели и один для кода. Начнем с типового проекта.
Я не хотел чего-то сложного; достаточно повторного использования простого примера: запрос отправляет строку, а ответ содержит префикс Hello
. Мы разрабатываем эту модель в специальном файле схемы Protobuf:
syntax = "proto3"; //1
package ch.frankel.blog.grpc.model; //2
option java_multiple_files = true; //3
option java_package = "ch.frankel.blog.grpc.model"; //3
option java_outer_classname = "HelloProtos"; //3
service HelloService { //4
rpc SayHello (HelloRequest) returns (HelloResponse) {
}
}
message HelloRequest { //5
string name = 1; //6
}
message HelloResponse { //7
string message = 1; //6
}
- Версия определения Protobuf
- Пакет
- Конфигурация для Java
- Определение службы
- Определение запроса
- Определение поля. Сначала идет тип, затем название и, наконец, порядок.
- Определение ответа
Мы будем использовать Maven для генерации шаблонного кода Java:
<project>
<dependencies>
<dependency>
<groupId>io.grpc</groupId> <!--1-->
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId> <!--1-->
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId> <!--1-->
<artifactId>jakarta.annotation-api</artifactId>
<version>1.3.5</version>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId> <!--2-->
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId> <!--3-->
<artifactId>protobuf-maven-plugin</artifactId>
<version>${protobuf-plugin.version}</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
- Зависимости во время компиляции
- Поиск информации об операционной системе. Используется в следующем плагине
- Создать код Java из файла
proto
После компиляции структура должна выглядеть примерно так:
Мы можем упаковать классы в JAR и использовать его в проекте веб-приложения. Последний есть в Kotlin, но только потому, что это мой любимый язык JVM.
Нам нужна только конкретная стартовая зависимость Spring Boot для интеграции конечных точек gRPC с Spring Boot:
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.14.0.RELEASE</version>
</dependency>
Вот важный бит:
@GrpcService //1
class HelloService : HelloServiceImplBase() { //2
override fun sayHello(
request: HelloRequest, //2
observer: StreamObserver<HelloResponse> //3
) {
with(observer) {
val reply = HelloResponse.newBuilder() //2
.setMessage("Hello ${request.name}") //4
.build()
onNext(reply) //5
onCompleted() //5
}
}
}
- Программа
grpc-server-spring-boot-starter
обнаруживает аннотацию и творит чудеса - Эталонные классы, созданные в указанном выше проекте
- Сигнатура метода допускает параметр
StreamObserver
. Этот класс взят изgrpc-stub.jar
.
- Получите запрос и добавьте к нему префикс, чтобы создать ответное сообщение.
- Воспроизвести события
Теперь мы можем запустить веб-приложение с помощью ./mvnw spring-boot:run
.
Тестирование службы gRPC
Смысл сообщения в том, что доступ к службе gRPC с помощью обычных инструментов невозможен. Тем не менее, для тестирования нам нужен специальный инструмент. Я нашел grpcurl. Давайте установим его и используем для получения списка доступных сервисов:
grpcurl --plaintext localhost:9090 list #1-2
- Список всех доступных служб gRPC без проверки TLS
- Чтобы избежать конфликтов между gRPC и другими каналами, например,, REST, Spring Boot использует другой порт
ch.frankel.blog.grpc.model.HelloService #1
grpc.health.v1.Health #2
grpc.reflection.v1alpha.ServerReflection #2
- Служба gRPC, которую мы определили
- Две дополнительные услуги, предоставляемые пользовательским стартером.
Мы также можем углубиться в структуру сервиса:
grpcurl --plaintext localhost:9090 describe ch.frankel.blog.grpc.model.HelloService
service HelloService {
rpc SayHello ( .ch.frankel.blog.grpc.model.HelloRequest ) returns ( .ch.frankel.blog.grpc.model.HelloResponse );
}
Наконец, мы можем вызвать службу с данными:
grpcurl --plaintext -d '{"name": "John"}' localhost:9090 ch.frankel.blog.grpc.model.HelloService/SayHello
{
"message": "Hello John"
}
Доступ к службе gRPC с помощью обычных инструментов
Представьте, что у нас есть обычное клиентское приложение JavaScript, которому требуется доступ к службе gRPC. Какие могут быть альтернативы?
Общий подход заключается в использовании grpc-web
:
Реализация gRPC на JavaScript для клиентов браузера. Дополнительные сведения, в том числе краткое руководство по началу работы, см. в документации по gRPC-web.
gRPC-веб-клиенты подключаются к gRPC-сервисам через специальный прокси; по умолчанию gRPC-web использует Envoy.
Мы ожидаем, что в будущем gRPC-web будет поддерживаться веб-платформами для конкретных языков, такими как Python, Java и Node. Подробнее см. в дорожной карте.
-- grpc-web
В описании указано единственное ограничение: работает только для JavaScript (на данный момент). Однако есть еще один. Это довольно навязчиво. Вам нужно получить файл proto
, сгенерировать шаблонный код и сделать так, чтобы ваш код вызывал его. Вы должны сделать это для каждого типа клиентов. Хуже того, если прото-файл изменится, вам придется перегенерировать клиентский код в каждом из них.
Однако существует альтернатива, если вы используете шлюз API. Я опишу, как это сделать с помощью Apache APISIX, но, возможно, другие шлюзы могут сделать то же самое. grpc-transcode — это подключаемый модуль, который позволяет перекодировать вызовы REST в gRPC и обратно. р>
Первый шаг — зарегистрировать прото-файл в Apache APISIX:
curl http://localhost:9180/apisix/admin/protos/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d "{ "content": "$(sed 's/"/"/g' ../model/src/main/proto/model.proto)" }"
Второй шаг — создать маршрут с помощью вышеуказанного плагина:
curl http://localhost:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"uri": "/helloservice/sayhello", #1
"plugins": {
"grpc-transcode": {
"proto_id": "1", #2
"service": "ch.frankel.blog.grpc.model.HelloService", #3
"method": "SayHello" #4
}
},
"upstream": {
"scheme": "grpc",
"nodes": {
"server:9090": 1
}
}
}'
- Определить детализированный маршрут
- Ссылка на прото-файл, определенный в предыдущей команде
- служба gRPC
- метод gRPC
На этом этапе любой клиент может отправить HTTP-запрос к указанной конечной точке. Apache APISIX перекодирует вызов gRPC, перенаправит его в указанную службу, получит ответ и снова перекодирует его.
curl localhost:9080/helloservice/sayhello?name=John
{"message":"Hello John"}
По сравнению с grpc-web
подход API Gateway позволяет совместно использовать файл proto
с одним компонентом: самим Gateway.
Преимущества перекодирования
На этом этапе мы можем использовать возможности шлюза API. Представьте, что нам нужно значение по умолчанию, если не передано name
, например,, World
. Разработчики с радостью установили бы его в коде, но любое изменение значения потребовало бы полной сборки и развертывания. Изменения могут быть почти мгновенными, если мы поместим значение по умолчанию в цепочку обработки маршрутов шлюза. Давайте соответствующим образом изменим наш маршрут:
curl http://localhost:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"uri": "/helloservice/sayhello",
"plugins": {
"grpc-transcode": {
...
},
"serverless-pre-function": { #1
"phase": "rewrite", #2
"functions" : [
"return function(conf, ctx) #3
local core = require("apisix.core")
if not ngx.var.arg_name then
local uri_args = core.request.get_uri_args(ctx)
uri_args.name = "World"
ngx.req.set_uri_args(uri_args)
end
end"
]
}
},
"upstream": {
...
}
}'
- Общий универсальный плагин, когда ни один не подходит
- Перепишите запрос
- Волшебный код Lua, который делает свое дело
Теперь мы можем выполнить запрос с пустым аргументом и получить ожидаемый результат:
curl localhost:9080/helloservice/sayhello?name
{"message":"Hello World"}
Заключение
В этом посте мы кратко описали gRPC и его преимущества для взаимодействия между службами. Мы разработали простую службу gRPC с использованием Spring Boot и grpc-server-spring-boot-starter
. Однако за это приходится платить: обычные клиенты не могут получить доступ к услуге. Нам пришлось прибегнуть к grpcurl
, чтобы протестировать его. То же самое касается клиентов на основе JavaScript или браузера.
Чтобы обойти это ограничение, мы можем использовать шлюз API. Я продемонстрировал, как настроить Apache APISIX с помощью подключаемого модуля grpc-transcode
для достижения желаемого результата.
Полный исходный код для этого поста можно найти на GitHub.
Дальше:
- gRPC
- Буферы протокола
- плагин os-maven
- Подключаемый модуль буферов протокола Maven
- gRPC-Spring-Boot-Starter
- grpcurl
- Apache APISIX
- плагин grpc-transcode
:::информация Первоначально опубликовано на сайте A Java Geek 16 марта 2023 г. сильный>
:::
Оригинал