gRPC — что это такое и здравствуй мир


Введение

Цель этой статьи — дать представление о том, что такое gRPC и пример того, как играть с официальным Hello World, используя IRIS Embedded Python.

Весь открытый код вы можете найти здесь, в репозитории этого проекта.

gRPC

gRPC (удаленный вызов процедур gRPC) — это архитектурный стиль API, основанный на протоколе RPC. Проект был создан компанией Google в 2015 году и лицензирован под Apache 2.0. В настоящее время проект поддерживается Cloud Native Computing Foundation (CNCF).

Успешные случаи его использования связаны с подключением сервисов между бэкендами, например, сервисов в архитектуре в стиле микросервисов.

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

Большинство протоколов на основе RPC используют IDL (язык описания интерфейса) для определения договора о взаимодействии между сервером и клиентом.

В gRPC используется формат механизма сериализатора под названием Protocol Buffer.

Назначение такого формата похоже на WSDL, где можно определить методы и структуры данных. Однако, в отличие от WSDL, который определяется с помощью XML, Protocol Buffer использует язык (язык буфера протокола), похожий на смесь большинства распространенных языков.

Например, чтобы определить сообщение для взаимодействия информации, вы можете использовать следующее определение буфера протокола:

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

Вы также можете определить контракты методов обслуживания для сообщений. Например:

// The greeter service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Использование буферов протокола в gRPC позволяет ему следовать функциональному, а не ресурсно-ориентированному принципу проектирования, используемому в REST.

Инструменты gRPC

То, что вы определяете на языке буферов протоколов, не может быть использовано напрямую. Вы должны транспонировать язык буферов протоколов в другой язык, который поддерживается gRPC.

Такая транспиляция выполняется пакетом под названием gRPC tools. В настоящее время платформа gRPC поддерживает такие языки, как Java, C++, Dart, Python, Objective-C, C#, Ruby, JavaScript и Go.

В этой статье мы будем использовать поддержку Python, чтобы использовать gRPC с функцией Embedded Python в IRIS.

Например, с помощью этой команды из инструментов gRPC вы можете транспонировать определение буферов протокола для службы Greeter, сообщения HelloRequest и HelloReply в Python:

python3 -m grpc_tools.protoc -I ../../protos --python_out=. --grpc_python_out=. ../../protos/helloworld.proto
Вход в полноэкранный режим Выйти из полноэкранного режима

Такая команда создает эти файлы Python:

-rwxrwxrwx  1 irisowner irisowner 2161 Mar 13 20:01 helloworld_pb2.py*
-rwxrwxrwx  1 irisowner irisowner 3802 Mar 13 20:01 helloworld_pb2_grpc.py*
Войти в полноэкранный режим Выйти из полноэкранного режима

Эти файлы представляют собой исходный код Python, сгенерированный из файла proto для сообщений и методов сервиса, соответственно. Сервер реализует служебные методы, а клиент (также называемый stub) вызывает их.

Таким образом, вы можете использовать Embedded Python для отправки/получения сообщений Hello через службу Greeter.

Еще одним полезным инструментом для gRPC является утилита grpcurl, эквивалентная curl. После представления нашего примера hello world будет представлен обзор того, как использовать такой инструмент.

Типы методов обслуживания

Клиенты могут различаться по тому, как они отправляют и получают сообщения от сервисных методов. Сообщения могут отправляться и приниматься по одному на вызов или в потоке. Для каждой комбинации в gRPC предусмотрен тип метода обслуживания:

  • Простой RPC или Unary: клиенты отправляют простое сообщение и получают простой ответ от сервера, т.е. стандартный вызов функции;
  • Response-streaming или серверный поток: клиенты отправляют простое сообщение и получают поток сообщений от сервера;
  • Request-streaming или клиентский поток: клиенты посылают поток сообщений и получают простое сообщение от сервера;
  • двунаправленный поток: клиенты посылают поток сообщений и получают другой поток сообщений от сервера.

Связь через такие методы может быть async (по умолчанию) или sync, в зависимости от потребностей клиента.

Страница gRPC core-concepts определяет эти типы как функции из жизненного цикла gRPC. Там также описаны другие возможности, которые не входят в рамки данного введения, но вы можете проверить ссылки ниже, если хотите получить больше информации:

  • Сроки/таймауты
  • Завершение RPC
  • отмена RPC
  • Метаданные
  • Каналы

Плюсы и минусы

Вот некоторые плюсы и минусы, которые я нашел в некоторых статьях:

Плюсы:

  • Более легкие сообщения. Буфер протокола — это двоичный формат, поэтому он позволяет избежать накладных расходов на JSON, создаваемых специальными символами.
  • Быстрая сериализация/десериализация. Опять же, благодаря своему двоичному формату, буферы протокола могут быть сериализованы/десериализованы в клиентские заглушки с помощью специальных языков без интерпретаторов.
  • Встроенные клиенты (заглушки). Буферы протоколов имеют встроенные генераторы для большинства используемых языков, в отличие от JSON, который зависит от сторонних инструментов, таких как OpenAPI и его генераторы клиентов.
  • Параллельные запросы. HTTP/1 допускает до 6 одновременных соединений, блокируя любые другие запросы, пока все 6 соединений не будут завершены — проблема, известная как HOL (head of line blocking); HTTP/2 устраняет такие ограничения.
  • Конструкция API, основанная на контрактах. Хотя REST API могут раскрывать контракт через сторонние инструменты, такие как OpenAPI, в gRPC такой контракт явно объявляется буферами протокола.
  • Нативная потоковая передача. Благодаря потоковым возможностям HTTP/2, gRPC позволяет использовать встроенную модель двунаправленного потока, в отличие от REST, который должен имитировать такое поведение по HTTP/1.

Минусы:

  • Отсутствие гибкости в буферах протокола (свободная связь с сервером).
  • Отсутствие человекочитаемости в буферах протоколов.
  • Гораздо большее количество специализированных людей, ресурсов и инструментов/проектов для REST/JSON, чем для gRPC/буферов протоколов.

Однако эти плюсы и минусы не являются консенсусом. Они сильно зависят от потребностей вашего приложения.

Например, предполагаемым недостатком REST/JSON является необходимость использования сторонних инструментов, таких как OpenAPI. Однако это может оказаться совсем не проблемой, поскольку такие инструменты активно поддерживаются, сопровождаются и используются несколькими сообществами разработчиков/компаниями по всему миру.

С другой стороны, если ваш проект должен иметь дело со сложностями, которые gRPC может решить лучше, чем REST, вам следует выбрать gRPC, даже если это решение повлечет за собой некоторые сложности, например, создание квалифицированной команды разработчиков.

Где/когда использовать gRPC?

Вот некоторые сценарии использования, когда вам нужен gRPC:

  • Коммуникация микросервисов
  • Клиент-серверное приложение, где клиенты работают на ограниченном оборудовании и/или в сети, предполагая, что HTTP/2 доступен
  • Интероперабельность, обеспечиваемая сильным дизайном контрактного API.

Игроки gRPC

Некоторые крупные технологические компании используют gRPC для решения конкретных задач:

  • Salesforce: gRPC повышает устойчивость платформы компании к взаимодействию благодаря сильной контрактной конструкции, обеспечиваемой буферами протокола.
  • Netflix: использует gRPC для улучшения своей среды микросервисов.
  • Spotify: как и Netflix, использует gRPC для решения проблем микросервисов и работы с большим количеством API.

Hello world с использованием встроенного Python

Итак, после краткого введения в то, что такое gRPC и что он делает, вы теперь можете управлять им, так что давайте немного поиграем. Как сказано в названии статьи, это просто адаптация оригинального примера hello world с использованием IRIS Embedded Python.

На самом деле, этот пример является переделкой двух других примеров, которые можно найти в репозитории образцов gRPC — helloworld и hellostreamingworld. С его помощью я хотел бы показать вам, как отправлять и получать простое сообщение в одиночном и потоковом режимах. Несмотря на то, что это простой пример без действительно полезных функций, он поможет вам понять основные концепции, связанные с разработкой gRPC-приложения.

Установка gRPC для Python

Сначала установим необходимый пакет для использования gRPC в Python. Если вы запускаете пример из контейнера, определенного в моем проекте на github, то, скорее всего, эти пакеты у вас уже установлены, поэтому этот шаг можно пропустить.

python3 -m pip install --upgrade pip
python3 -m pip install --upgrade --target /usr/irissys/mgr/python grpcio grpcio-tools
Вход в полноэкранный режим Выход из полноэкранного режима

Определение контракта на обслуживание

Теперь давайте посмотрим на контракт сервиса, то есть на файл буфера протокола (или просто protobuf для краткости), со схемой сообщений и доступными методами:

syntax = "proto3";

package helloworld;

// The greeting service definition.
service MultiGreeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  // Sends multiple greetings
  rpc SayHelloStream (HelloRequest) returns (stream HelloReply) {}
}

// The request message containing the user's name and how many greetings
// they want.
message HelloRequest {
  string name = 1;
  Int32 num_greetings = 2;
}

// A response message containing a greeting
message HelloReply {
  string message = 1;
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Этот файл protobuf определяет сервис под названием MultiGreeter с двумя методами RPC, SayHello() и SayHelloStream().

Метод SayHello() получает сообщение HelloRequest и отправляет сообщение HelloReply. Аналогично, метод SayHelloStream() получает и отправляет те же сообщения, но он отправляет поток сообщений HelloRequest, а не одно сообщение.

После определения сервиса идут определения сообщений, HelloRequest и HelloReply. Сообщение HelloRequest содержит два поля: строковое name и целочисленное num_greetings. Сообщение HelloReply содержит только одно поле, строковое message.

Цифры после полей называются номерами полей. Эти номера не должны быть изменены после использования сообщения, поскольку они служат в качестве идентификаторов.

Генерация кода Python из контракта на обслуживание

Как вы, вероятно, заметили, нам не нужно писать никакого кода в определении protobuf, только интерфейсы. Задачу реализации кода для различных языков программирования выполняет компилятор protobuf protoc. Существует компилятор protoc для каждого из языков, поддерживаемых gRPC.

Для Python компилятор развернут в виде модуля grpc_tools.protoc.

Чтобы скомпилировать определение protobuf в код Python, выполните следующую команду (предполагается, что вы используете мой проект):

cd /irisrun/repo/jrpereira/python/grpc-test
/usr/irissys/bin/irispython -m grpc_tools.protoc -I ./ --python_out ./ --grpc_python_out ./ helloworld.proto
Войти в полноэкранный режим Выйти из полноэкранного режима

Эта команда вызывает модуль grpc_tools.protoc — компилятор protoc для Python, со следующими параметрами:

В этом случае все эти параметры расположения устанавливаются для текущего каталога.

Код, сгенерированный компилятором protoc, не является лучшим примером читабельности, хотя понять его не так уж сложно. Вы можете посмотреть его в каталоге, переданном компилятору protoc.

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

Реализация сервера для сервиса

Чтобы реализовать сервер для сервиса, определенного выше, воспользуемся Embedded Python.

Сначала определим сервер с помощью Python-файла, в котором будет реализована логика сервера. Я решил реализовать это таким образом из-за необходимости использования библиотеки параллелизма Python.

"""
The Python implementation of the GRPC helloworld.Greeter server.
Adapted from:
    - https://github.com/grpc/grpc/blob/master/examples/python/helloworld/async_greeter_server.py
    - https://github.com/grpc/grpc/blob/master/examples/python/hellostreamingworld/async_greeter_server.py
    - https://groups.google.com/g/grpc-io/c/6Yi_oIQsh3w
"""

from concurrent import futures
import logging
import signal
from typing import Any
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter

import grpc
from helloworld_pb2 import HelloRequest, HelloReply
from helloworld_pb2_grpc import MultiGreeterServicer, add_MultiGreeterServicer_to_server

import iris

NUMBER_OF_REPLY = 10

parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
parser.add_argument("-p", "--port", default="50051", help="Server port")
args = vars(parser.parse_args())

class Greeter(MultiGreeterServicer):

    def SayHello(self, request: HelloRequest, context) -> HelloReply:
        logging.info("Serving SayHello request %s", request)
        obj = iris.cls("dc.jrpereira.gRPC.HelloWorldServer")._New()
        # hook to your ObjectScript code
        return obj.SayHelloObjectScript(request)

    def SayHelloStream(self, request: HelloRequest, context: grpc.aio.ServicerContext) -> HelloReply:
        logging.info("Serving SayHelloStream request %s", request)
        obj = iris.cls("dc.jrpereira.gRPC.HelloWorldServer")._New()
        n = request.num_greetings
        if n == 0:
            n = NUMBER_OF_REPLY
        for i in range(n):
            # hook to your ObjectScript code
            yield obj.SayHelloObjectScript(request)

def get_server():
    port = args["port"]
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    add_MultiGreeterServicer_to_server(Greeter(), server)
    listen_addr = f"[::]:{port}"
    server.add_insecure_port(f"[::]:{port}")
    logging.info("Starting server on %s", listen_addr)
    return server

def handle_sigterm(*_: Any) -> None :
    """Shutdown gracefully."""
    done_event = server.stop(None)
    done_event.wait(None)
    print('Stop complete.')

logging.basicConfig(level=logging.INFO)

server = get_server()
server.start()

# https://groups.google.com/g/grpc-io/c/6Yi_oIQsh3w
signal.signal(signal.SIGTERM, handle_sigterm)

server.wait_for_termination()
Вход в полноэкранный режим Выйти из полноэкранного режима

Как вы можете видеть, здесь реализованы методы, определенные в спецификации protobuf — SayHello() и SayHelloStream().

Метод SayHello() просто отправляет одно значение, тогда как метод SayHelloStream() возвращает клиенту количество сообщений, равное NUMBER_OF_REPLY, посредством оператора Python yield.

Также обратите внимание, что я создал хук для внедрения логики, определенной в ObjectScript. Так, я определил метод SayHelloObjectScript в классе dc.jrpereira.gRPC.HelloWorldServer:

Method SayHelloObjectScript(request)
{
    Set sys = $system.Python.Import("sys")
    Do sys.path.append("/usr/irissys/mgr/python/grpc-test/")

    Set helloworldpb2 = $system.Python.Import("helloworld_pb2")

    Set reply = helloworldpb2.HelloReply()
    Set reply.message = "Hi "_request.name_"! :)"

    Return reply
}
Вход в полноэкранный режим Выход из полноэкранного режима

Таким образом, вы можете получать запросы от клиентов gRPC из Python и обрабатывать их, используя смесь логики, написанной на Python и ObjectScript.

Реализация клиента для службы

Поскольку код клиента не требует параллелизма, я реализовал его с помощью кода Python непосредственно в классе ObjectScript:

ClassMethod ExecutePython() [ Language = python ]
{
    import sys
    sys.path.append('/usr/irissys/mgr/python/grpc-test/')

    import grpc
    from helloworld_pb2 import HelloRequest
    from helloworld_pb2_grpc import MultiGreeterStub

    channel = grpc.insecure_channel('localhost:50051')
    stub = MultiGreeterStub(channel)

    response = stub.SayHello(HelloRequest(name='you'))
    print("Greeter client received: " + response.message)

    for response in stub.SayHelloStream(HelloRequest(name="you")):
        print("Greeter client received from stream: " + response.message)
}
Вход в полноэкранный режим Выход из полноэкранного режима

Сначала мы добавляем каталог grpc-test в путь Python, чтобы иметь возможность импортировать сгенерированный код protobuf.

Затем создается соединение с localhost на порту 50051 и заглушка (или клиент).

С помощью такого клиента мы можем запрашивать информацию для сервера, слушающего на localhost:50051, через методы SayHello() и SayHelloStream().

Метод SayHello() возвращает только одно значение, поэтому нам нужно просто сделать запрос и использовать его ответ. С другой стороны, метод SayHelloStream() возвращает поток данных в коллекции, поэтому нам нужно просмотреть его, чтобы получить все данные.

Тестирование кода

Хорошо, теперь давайте протестируем наш код.

Вы можете проверить весь этот код в моем проекте hello world. Выполните следующие шаги, чтобы запустить его:

git clone https://github.com/jrpereirajr/iris-grpc-example
cd iris-grpc-example
docker-compose up -d
Войдите в полноэкранный режим Выйдите из полноэкранного режима

Затем откройте терминал IRIS через системный терминал или через Visual Studio Code:

docker exec -it iris-grpc-example_iris_1 bash
iris session iris
Войти в полноэкранный режим Выйти из полноэкранного режима

Запустите наш gRPC-сервер:

Set server = ##class(dc.jrpereira.gRPC.HelloWorldServer).%New()
Do server.Start()
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь давайте создадим gRPC-клиент для взаимодействия с этим сервером:

Set client = ##class(dc.jrpereira.gRPC.HelloWorldClient).%New()
Do client.ExecutePython()
Войти в полноэкранный режим Выйти из полноэкранного режима

Если все в порядке, вы должны увидеть в терминале кучу приветственных сообщений.

Наконец, давайте остановим сервер:

Do server.Stop()
Войти в полноэкранный режим Выйти из полноэкранного режима

Использование утилиты grpcurl в нашем hello world

Как я уже говорил, утилита grpcurl является аналогом curl, но здесь вместо того, чтобы действовать как HTTP-клиент (как curl), мы используем grpcurl как gRPC-клиент для тестирования сервисов запущенного gRPC-сервера. Итак, давайте используем его, чтобы еще немного поиграть с нашим миром hello.

Во-первых, давайте загрузим и установим утилиту grpcurl:

cd /tmp
wget https://github.com/fullstorydev/grpcurl/releases/download/v1.8.6/grpcurl_1.8.6_linux_x86_64.tar.gz
tar -zxvf grpcurl_1.8.6_linux_x86_64.tar.gz
Вход в полноэкранный режим Выйти из полноэкранного режима

Проверьте, все ли в порядке с установкой, набрав:

./grpcurl --help
Войти в полноэкранный режим Выйти из полноэкранного режима

Если все в порядке, вы должны получить вывод со всеми опциями grpcurl.

Теперь спросим, какие службы доступны на сервере:

./grpcurl 
    -plaintext 
    -import-path /irisrun/repo/jrpereira/python/grpc-test 
    -proto helloworld.proto 
    localhost:50051 
    list
Войти в полноэкранный режим Выйти из полноэкранного режима

Вы должны получить следующий ответ:

helloworld.MultiGreeter
Войти в полноэкранный режим Выйти из полноэкранного режима

Как вы можете видеть, утилита вернула наш сервис, определенный в файле proto (helloworld.MultiGreeter), в качестве ответа для перечисления всех доступных сервисов.

В приведенной выше команде я поместил каждый параметр в отдельную строку. Итак, давайте объясним каждый из них:

-plaintext: позволяет использовать gRPC без TLS (небезопасный режим); мы используем его здесь, потому что не реализовали безопасное соединение для нашего сервера. Конечно, его следует использовать только в непроизводственной среде.

После этих параметров мы указываем имя хоста сервера и порт, а затем команду grpcurllist в данном случае.

Теперь запросим все методы в сервисе helloworld.MultiGreeter:

./grpcurl 
    -plaintext 
    -import-path /irisrun/repo/jrpereira/python/grpc-test 
    -proto helloworld.proto 
    localhost:50051 
    list helloworld.MultiGreeter
Войти в полноэкранный режим Выйти из полноэкранного режима

Вы должны получить следующее сообщение:

helloworld.MultiGreeter.SayHello
helloworld.MultiGreeter.SayHelloStream
Вход в полноэкранный режим Выход из полноэкранного режима

Как вы видите, это методы, определенные в файле proto, используемом для генерации кода для нашего сервера.

Хорошо, теперь давайте протестируем метод SayHello():

./grpcurl 
    -plaintext  
    -d '{"name":"you"}' 
    -import-path /irisrun/repo/jrpereira/python/grpc-test 
    -proto helloworld.proto 
    localhost:50051 
    helloworld.MultiGreeter.SayHello
Войти в полноэкранный режим Выйти из полноэкранного режима

Вот ожидаемый результат (точно такой же, как тот, что был реализован нашим клиентом ранее):

{
  "message": "Hi you! :)"
}
Вход в полноэкранный режим Выход из полноэкранного режима

Также давайте протестируем другой метод, SayHelloStream():

./grpcurl 
    -plaintext -d '{"name":"you"}' 
    -import-path /irisrun/repo/jrpereira/python/grpc-test 
    -proto helloworld.proto localhost:50051 
    helloworld.MultiGreeter.SayHelloStream
Вход в полноэкранный режим Выход из полноэкранного режима

И мы должны получить поток с 10 приветствиями:

{
  "message": "Hi you! :)"
}
{
  "message": "Hi you! :)"
}
...
{
  "message": "Hi you! :)"
}
Вход в полноэкранный режим Выход из полноэкранного режима

Наконец, давайте немного изменим эту команду, чтобы использовать другое свойство в сообщении protobuf, num_greetings. Это свойство используется сервером для управления тем, сколько сообщений будет отправлено в потоке.

Таким образом, эта команда просит сервер вернуть только 2 сообщения в потоке, вместо 10 по умолчанию:

./grpcurl 
    -plaintext -d '{"name":"you", "num_greetings":2}' 
    -import-path /irisrun/repo/jrpereira/python/grpc-test 
    -proto helloworld.proto localhost:50051 
    helloworld.MultiGreeter.SayHelloStream
Войти в полноэкранный режим Выйти из полноэкранного режима

И вот что вы увидите в терминале:

{
  "message": "Hi you! :)"
}
{
  "message": "Hi you! :)"
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Заключение

В этой статье был дан обзор gRPC с его плюсами и минусами — в основном по сравнению с REST. Также были протестированы некоторые примеры его использования с IRIS, путем адаптации некоторых примеров для Python, представленных в официальном репозитории gRPC.

Как было сказано ранее, gRPC имеет несколько примеров использования, и совместимость является одним из возможных, поэтому создание адаптера IRIS Interoperability — это естественный путь к практическому использованию gRPC в IRIS.

Однако это потребует больше усилий, поэтому это будет темой другой статьи. =)

Надеюсь, представленная здесь информация была вам полезна! До встречи!

Ссылки

https://grpc.io/docs/what-is-grpc/introduction/
https://developers.google.com/protocol-buffers
https://en.wikipedia.org/wiki/GRPC
https://www.imaginarycloud.com/blog/grpc-vs-rest/
https://www.vineethweb.com/post/grpc/
https://www.capitalone.com/tech/software-engineering/grpc-framework-for-microservices-communication/
https://www.altexsoft.com/blog/what-is-grpc/

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