Как стать автором
Обновить
739.3
OTUS
Цифровые навыки от ведущих экспертов

Реализуем балансировку нагрузки на сервер с помощью Envoy и gRPC API на Kotlin

Время на прочтение6 мин
Количество просмотров2K
Автор оригинала: Faisal Masood

В наши дни архитектуры на базе микросервисов стали внедряться практически повсеместно. И нередки ситуации, когда какая-нибудь бизнес-функция может генерировать большое количество сетевого трафика в форме обмена сообщениями между несколькими микросервисами, которые она использует. Если мы сможем сделать способ передачи сообщений более эффективным за счет, например, уменьшения размера сообщений, то мы сможем использовать ту же инфраструктуру для работы с более высокими нагрузками.

Protobuf (сокращение от «protocol buffers») предоставляет независящие от языка и платформы механизмы сериализации структурированных данных для использования в коммуникационных протоколах, хранилищах данных и т. д. gRPC — это современный фреймворк удаленного вызова процедур («remote procedure call» — RPC) с открытым исходным кодом, который может работать где угодно. Их сочетание позволяет создать эффективный формат сообщений, который автоматически сжимается и обеспечивает первоклассную поддержку сложных структур данных, а также ряд других преимуществ (в отличие от JSON).

Микросервисные среды требуют большого количества коммуникаций между сервисами, и для этого между сервисами должен быть согласован ряд моментов. Они должны иметь согласованое API для обмена данными, например, POST (или PUT) и GET для отправки и получения сообщений. Они также должны договориться о формате данных (JSON). Клиентам, вызывающим сервис, также необходимо позаботиться о написании кода для удаленных вызовов (фреймворки!). Protobuf и gRPC предоставляют возможность определить схему сообщения (а JSON — нет) и сгенерировать скелет программы для использования gRPC‑сервиса (без фреймворков).

Хоть JSON — это человекочитаемый формат со вложенной структурой данных, у него есть несколько недостатков, например, отсутствие схемы, объекты могут быть довольно громоздкими, и кое‑где может не хватать комментариев.

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

Так что же такое gRPC и Protobuf?

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

Я рекомендовал бы вам в качестве практики создать сервер потоковой передачи данных в формате JSON по HTTP. Тогда вы поймете, о чем я говорю. Потоковая передача данных встроена в gRPC. О концепциях gRPC можно почитать здесь. Лично мне gRPC чем-то напоминает CORBA.

Protobuf — это инструмент для сериализации данных. Protobuf предоставляет возможность определять полностью типизированные схемы для сообщений. Он также позволяет вставлять документацию прямо в само сообщение.

gRPC использует HTTP/2 с постоянным соединением и мультиплексированием для повышения производительности по сравнению с сервисами, работающими на REST по HTTP 1.1. Однако у постоянного соединения есть проблемы с прокси 4-го уровня. Нам нужен прокси, поддерживающий балансировку нагрузки на 7-м уровне. Envoy как раз может проксировать вызовы gRPC с поддержкой балансировки нагрузки на стороне сервере. Envoy также обеспечивает обнаружение сервисов на основе внешнего сервиса, известного как EDS, и я покажу вам, как использовать и эту функцию.

Что мы будем создавать

В этой статье я создаю gRPC-сервис на основе Kotlin. Я буду балансировать нагрузку между несколькими инстансами моего сервиса с помощью прокси Enovy. А также я настрою простой REST-сервис, который обеспечит обнаружение сервиса для Envoy. Базовая архитектура выглядит следующим образом.

Basic architecture
Basic architecture

Подготовка компонентов

Во-первых, нам нужно определить сообщение Protobuf, которое будет служить контрактом между клиентом и сервером (полный файл см. в event.proto):

syntax  = "proto3";
import "google/protobuf/empty.proto";
package event;

option java_package = "com.proto.event";
option java_multiple_files = true;

message Event {
    int32 event_id = 1;
    string event_name = 2;
    repeated string event_hosts = 3;
}

enum EVENT_TYPE {
    UNDECLARED = 0;
    BIRTHDAY = 1;
    MARRIAGE = 2;
}

message CreateEventResponse{
    string success = 1;
}

message AllEventsResponse{
    Event event = 1;
}

service EventsService{
    rpc CreateEvent(Event) returns (CreateEventResponse) {};
    rpc AllEvents(google.protobuf.Empty) returns (stream AllEventsResponse) {};
}

Это сообщение будет использовано плагином Gradle gRPC для генерации заглушек. Эти заглушки будет использовать код клиента и сервера. Чтобы сгенерировать заглушки, вы можете запустить задачу Gradle generateProto.

Теперь пришло время написать сервер:

val eventServer = ServerBuilder.forPort(50051)
.addService(EventsServiceImpl()) //серверная реализация
.build()
eventServer.start()
println("Event Server is Running now!")

Runtime.getRuntime().addShutdownHook( Thread{
eventServer.shutdown()
} )

eventServer.awaitTermination()

После того как мы расправились с основным кодом сервера, мы напишем бизнес-логику, которая выводит захардкоженное сообщение и возвращает фиксированный ответ.

override fun createEvent(request: Event?, responseObserver: StreamObserver<CreateEventResponse>?) {
        println("Event Created ")
        responseObserver?.onNext(CreateEventResponse.newBuilder().setSuccess("true").build())
        responseObserver?.onCompleted()
    }

Далее давайте напишем клиент, который будет использовать наш сервис событий:

fun main(args: Array<String>) {
    var eventsChannel = ManagedChannelBuilder.forAddress("10.0.0.112", 8080)
            .usePlaintext()
            .build()

    var eventServiceStub = EventsServiceGrpc.newBlockingStub(eventsChannel)

    for(i in 1..20) {
        eventServiceStub.createEvent(Event.newBuilder().setEventId(i).setEventName("Event $i").build())
    }

    eventsChannel.shutdown()
}

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

Конфигурация прокси-сервера Envoy состоит из трех частей. Все эти настройки находятся в файле envoy.yaml. Убедитесь, что вы изменили IP-адрес EDS-сервиса в соответствии с вашими настройками. Обновить IP-адрес сервиса можно в файле EDSServer.kt.

Теперь определим фронтенд-сервис. Этот сервис будет принимать запросы от клиентов.

  listeners:
        - name: envoy_listener
          address:
            socket_address: { address: 0.0.0.0, port_value: 8080 }
          filter_chains:
            - filters:
                - name: envoy.http_connection_manager
                  config:
                    stat_prefix: ingress_http
                    codec_type: AUTO
                    route_config:
                      name: local_route
                      virtual_hosts:
                        - name: local_service
                          domains: ["*"]
                          routes:
                            - match: { prefix: "/" }
                              route: { cluster: grpc_service }
                    http_filters:
                      - name: envoy.router
     

Определим бекенд-сервис (его имя grpc_service в файле envoy.yaml). Бекенд-сервис будет заниматься балансировкой нагрузки вызовов этой группы серверов. Обратите внимание, что мы не знаем фактическое местоположение бекенд-сервиса. Местоположение бекенд-сервиса (service discovery) предоставляется через EDS-сервис. Чуть-чуть дальше мы поговорим об определении конечной точки EDS.

        - name: grpc_service
          connect_timeout: 5s
          lb_policy: ROUND_ROBIN
          http2_protocol_options: {}
          type: EDS
          eds_cluster_config:
            eds_config:
              api_config_source:
                api_type: REST
                cluster_names: [eds_cluster]
                refresh_delay: 5s

По желанию вы можете определить конечную точку EDS. (Вы также можете предоставить фиксированный список серверов). Это еще один сервис, который предоставляет список конечных точек бекенда. Таким образом, Envoy сможет динамически подстраиваться под доступные серверы. Я написал этот EDS-сервис в виде простого класса.

   - name: eds_cluster
          connect_timeout: 5s
          type: STATIC
          hosts: [{ socket_address: { address: 10.0.0.112, port_value: 7070 }}]

Запуск проекта

Скопируйте проект локально:

git clone https://github.com/masoodfaisal/grpc-example.git

Соберите проект с помощью Gradle:

cd grpc-example
./gradlew generateProto
./gradlew build

Поднимите EDS-сервер, чтобы обеспечить обнаружение сервисов для прокси-сервера Envoy:

cd grpc-example
./gradlew -PmainClass=com.faisal.eds.EDSServerKt execute

Инициализируйте несколько инстансов сервиса:

cd grpc-example
./gradlew -PmainClass=com.faisal.grpc.server.EventServerKt execute
./gradlew -PmainClass=com.faisal.grpc.server.EventServer2Kt execute

Запустите прокси Enovy:

cd envoy-docker
docker build -t envoy:grpclb .
docker run -p 9090:9090 -p 8080:8080 envoy:grpclb

Мой клиент выполняет вызов в цикле, который демонстрирует, как нагрузка распределяется по кругу.

./gradlew -PmainClass=com.faisal.grpc.client.EventClientKt execute

Заключение

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

И пусть ваш следующий сервис будет с gRPC!


На рынке труда не многие Kotlin-разработчики могут похвастаться навыкам работы с Kotlin DSL. Однако он по факту стал неотъемлемой частью экосистемы Kotlin. Владение этим инструментом является одним из показателей квалификации разработчика. Приглашаем всех желающих на открытый урок, на котором разложим Kotlin DSL по полочкам:

  • обсудим, что это такое и когда его уместно применять;

  • из каких элементов он состоит;

  • на практике напишем несложный пример.

Записаться на урок можно бесплатно на странице курса «Kotlin Backend Developer. Professional».

Теги:
Хабы:
Всего голосов 9: ↑9 и ↓0+12
Комментарии1

Публикации

Информация

Сайт
otus.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия
Представитель
OTUS