Привет, Хабр!

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

gRPC – высокопроизводительный и мощный инструмент для построения микросервисных систем.

Основы gRPC

gRPC - это высокопроизводительный фреймворк удаленного вызова процедур, разработанный компанией Google. Он основан на простой и эффективной идее - определении удаленных сервисов и методов с использованием Protocol Buffers, а затем создании клиентских и серверных стабов для эффективного взаимодействия между приложениями. Почему это важно?

  • Производительность: gRPC использует бинарный протокол, что делает его намного более эффективным по сравнению с текстовыми протоколами, такими как JSON. Он также поддерживает множество языков программирования, что обеспечивает высокую производительность в разнородных экосистемах.

  • Определение интерфейса: С gRPC, вы определяете ваши сервисы и методы с использованием gRPC Interface Definition Language (IDL), что обеспечивает строгий и однозначный интерфейс между клиентом и сервером. Это помогает избежать недоразумений и облегчает поддержку кода.

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

Преимущества gRPC перед REST API:

  • Эффективность и производительность: Как уже упоминалось, gRPC использует бинарный формат для обмена данными, что приводит к меньшей нагрузке на сеть и более быстрой сериализации и десериализации данных.

  • Автоматическая генерация кода: gRPC автоматически генерирует клиентские и серверные стабы на основе определений в Proto файле. Это упрощает разработку и уменьшает риск ошибок в коде.

  • Поддержка множества языков: Вы можете использовать gRPC с большим количеством языков программирования, что делает его идеальным для гетерогенных микросервисных архитектур.

Основные компоненты gRPC: Protocol Buffers, gRPC IDL и gRPC стабы:

  • Protocol Buffers (Proto): Это язык описания данных, разработанный Google. Он позволяет вам определить структуру данных и сервисы, которые будут использоваться вашими микросервисами. Proto файлы становятся источником истины для клиентов и серверов и служат как основа для генерации кода.

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

  • gRPC стабы: Это сгенерированный код, который предоставляет вам клиентский и серверный интерфейс для ваших методов. Они упрощают вам работу и обеспечивают автоматическую сериализацию и десериализацию данных.

Установка и настройка gRPC для разработки

Чтобы начать работу с gRPC, вам нужно установить библиотеки для вашего языка программирования. Например, для языка Python, вы можете использовать pip:

pip install grpcio
pip install grpcio-tools

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

Проектирование микросервисов с использованием gRPC

Разделение функциональности

При проектировании микросервисов, важно правильно разделить функциональность. Каждый микросервис должен быть ответственен за определенную, хорошо определенную часть функциональности. Это облегчит разработку, масштабирование и обслуживание вашей системы.

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

Определение gRPC сервисов и методов

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

syntax = "proto3";

package ecommerce;

service OrderService {
  rpc CreateOrder (OrderRequest) returns (OrderResponse);
}

message OrderRequest {
  // Поля запроса
}

message OrderResponse {
  // Поля ответа
}

В этом примере у нас есть сервис "OrderService", который предоставляет метод "CreateOrder".

Проектирование сообщений с использованием Protocol Buffers

Сообщения Protocol Buffers - это способ определения данных, которые будут передаваться между клиентом и сервером. Пример определения сообщения в Proto файле:

message Order {
  string order_id = 1;
  repeated Product products = 2;
}

message Product {
  string product_id = 1;
  int32 quantity = 2;
}

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

Работа с схемами данных и версионированием

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

Пример версионирования в Proto файле:

syntax = "proto3";

package ecommerce;

// Версия 1
message OrderV1 {
  string order_id = 1;
}

// Версия 2
message OrderV2 {
  string order_id = 1;
  repeated Product products = 2;
}

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

Коммуникация между микросервисами

Методы вызова gRPC сервисов

gRPC предоставляет несколько методов вызова сервисов:

  1. Унарные методы: Один запрос и один ответ. Пример:

rpc GetOrder(OrderRequest) returns (OrderResponse);
  1. Серверные методы: Один запрос и множество ответов. Пример:

rpc GetOrders(OrderRequest) returns (stream Order);
  1. Клиентские методы: Множество запросов и один ответ. Пример:

rpc CreateOrder(stream OrderRequest) returns (OrderResponse);
  1. Двусторонние методы: Множество запросов и множество ответов. Пример:

rpc Chat(stream ChatMessage) returns (stream ChatMessage);

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

Обработка ошибок и повторов вызовов

При коммуникации между микросервисами, необходимо уделить внимание обработке ошибок и повторам вызовов. gRPC предоставляет механизмы для управления ошибками, включая стандартные коды ошибок (например, OK, NOT_FOUND, INVALID_ARGUMENT) и возможность создания пользовательских ошибок.

Пример обработки ошибки на стороне клиента (на языке Python):

try:
    response = stub.GetOrder(order_request)
except grpc.RpcError as e:
    if e.code() == grpc.StatusCode.NOT_FOUND:
        # Обработка ошибки NOT_FOUND
    else:
        # Обработка других ошибок

Также, gRPC предоставляет возможность реализации повторов вызовов с помощью агентов перезапуска (retries) и обратных офферов (backoff), что помогает в случае временных сбоев.

Аутентификация и авторизация в gRPC

Безопасность - это критически важный аспект в микросервисных архитектурах. gRPC предоставляет возможности для аутентификации и авторизации, включая поддержку механизмов аутентификации, таких как OAuth, JWT, и TLS.

Пример использования JWT для аутентификации:

rpc CreateOrder (OrderRequest) returns (OrderResponse) {
  option (google.api.http) = {
    post: "/v1/orders"
    body: "*"
  };
  option (grpc.gateway.protoc_gen_grpc.gateway_http) = {
    rules: {
      post: "/v1/orders"
      put: "/v1/orders/{order_id}"
      body: "*"
      response_body: "*"
      custom: {
        name: "authorization"
        pattern: "Bearer [^ ]+"
        value: "{authorization}"
        remove: "Authorization"
      }
    };
  };
  option (grpc.gateway.protoc_gen_grpc.gateway_http_get) = {
    binding: "*"
  };
}

Управление транзакциями и согласованностью

Управление транзакциями в микросервисной архитектуре - это сложная задача. gRPC не предоставляет встроенной поддержки для транзакций, но вы можете использовать другие технологии, такие как Apache Kafka или Apache Pulsar, для обеспечения согласованности данных между микросервисами.

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

Масштабирование и управление микросервисами

Масштабирование gRPC микросервисов

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

Пример масштабирования с использованием Docker и Kubernetes:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
      - name: order-service
        image: your/order-service-image

В этом примере мы создаем Deployment в Kubernetes с тремя репликами для микросервиса. Как только нагрузка растет, Kubernetes может автоматически масштабировать количество реплик.

Управление версиями и обновлениями

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

Пример использования версионирования в gRPC:

syntax = "proto3";

package ecommerce;

// Версия 1
message OrderV1 {
  string order_id = 1;
}

// Версия 2
message OrderV2 {
  string order_id = 1;
  repeated Product products = 2;
}

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

Мониторинг и журналирование для отладки и анализа производительности

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

Для мониторинга и журналирования вы можете использовать инструменты, такие как Prometheus, Grafana, ELK (Elasticsearch, Logstash, Kibana) stack и Zipkin. Пример интеграции Prometheus в gRPC:

func main() {
  // Инициализация мониторинга Prometheus
  prometheus.MustRegister(server)

  lis, err := net.Listen("tcp", ":50051")
  if err != nil {
    log.Fatalf("failed to listen: %v", err)
  }
  s := grpc.NewServer()

  pb.RegisterOrderServiceServer(s, &server)
  reflection.Register(s)

  go func() {
    if err := http.ListenAndServe(":8080", promhttp.Handler()); err != nil {
      log.Fatalf("failed to serve: %v", err)
    }
  }()

  if err := s.Serve(lis); err != nil {
    log.Fatalf("failed to serve: %v", err)
  }
}

Пример выше интегрирует мониторинг Prometheus в ваш gRPC сервер и предоставляет метрики по HTTP для мониторинга.

Примеры реализации

Пример 1: Микросервис для управления заказами

Допустим, у нас есть магазин с онлайн-продажами и мы хотим разработать микросервис для управления заказами. Мы можем создать gRPC сервис с методами для создания, просмотра и обновления заказов. Этот микросервис может использовать Protocol Buffers для определения сообщений, например, для заказов и продуктов. Мы также должны обеспечить мониторинг с использованием Prometheus и Grafana и журналирование с помощью ELK stack для отладки и анализа производительности.

Пример определения gRPC сервиса:

syntax = "proto3";

package ecommerce;

service OrderService {
  rpc CreateOrder (OrderRequest) returns (OrderResponse);
  rpc GetOrder (GetOrderRequest) returns (OrderResponse);
  rpc UpdateOrder (UpdateOrderRequest) returns (OrderResponse);
}

message OrderRequest {
  string user_id = 1;
  repeated Product products = 2;
}

message OrderResponse {
  string order_id = 1;
  string status = 2;
}

message GetOrderRequest {
  string order_id = 1;
}

message UpdateOrderRequest {
  string order_id = 1;
  string new_status = 2;
}

Пример 2: Микросервис для управления инвентарем

Допустим, у нас есть еще один микросервис для управления инвентарем товаров. Мы можем создать gRPC сервис с методами для добавления, удаления и обновления товаров в инвентаре. Этот микросервис также использует Protocol Buffers для определения сообщений и предоставляет мониторинг и журналирование для отладки.

Пример определения gRPC сервиса:

syntax = "proto3";

package ecommerce;

service InventoryService {
  rpc AddProduct (AddProductRequest) returns (AddProductResponse);
  rpc RemoveProduct (RemoveProductRequest) returns (RemoveProductResponse);
  rpc UpdateProduct (UpdateProductRequest) returns (UpdateProductResponse);
}

message AddProductRequest {
  string product_id = 1;
  int32 quantity = 2;
}

message AddProductResponse {
  string product_id = 1;
  int32 new_quantity = 2;
}

message RemoveProductRequest {
  string product_id = 1;
}

message RemoveProductResponse {
  string product_id = 1;
}

message UpdateProductRequest {
  string product_id = 1;
  int32 new_quantity = 2;
}

message UpdateProductResponse {
  string product_id = 1;
  int32 new_quantity = 2;
}

Пример 3: Аутентификация и авторизация

Для обеспечения безопасности микросервисов мы можем использовать механизм аутентификации и авторизации. Допустим, мы решаем использовать JWT для аутентификации. Мы добавляем проверку токена JWT к нашему gRPC серверу и определяем, какие роли и права есть у пользователей для обеспечения авторизации.

Пример добавления аутентификации в gRPC сервер (на языке Go):

func main() {
  // Инициализация gRPC сервера
  // ...

  // Добавление аутентификации JWT
  interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    token, err := parseJWTFromContext(ctx)
    if err != nil {
      return nil, status.Errorf(codes.Unauthenticated, "Authentication failed: %v", err)
    }
    if !validateJWT(token) {
      return nil, status.Errorf(codes.PermissionDenied, "Permission denied")
    }
    return handler(ctx, req)
  }

  opts := []grpc.ServerOption{
    grpc.UnaryInterceptor(interceptor),
  }

  // Создание gRPC сервера с опцией аутентификации
  s := grpc.NewServer(opts...)

  pb.RegisterOrderServiceServer(s, &server)

  if err := s.Serve(lis); err != nil {
    log.Fatalf("failed to serve: %v", err)
  }
}

Заключение

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

Комментарии (7)


  1. mikegordan
    19.10.2023 16:24
    +2

    А как сейчас в grpc реализуется API gateway? Или Service load balancer ?


    1. ptr128
      19.10.2023 16:24

      https://cloud.google.com/api-gateway/docs/grpc-overview

      С load balancing все сложнее, так как чрезмерное увлечение двунаправленным потоковым gRPC, фактически, ставит крест на северном балансере, сохраняя только клиентский. Но тут уже, простите, что называется "включайте голову". Странно звучат претензии к балансированию потокового обмена, которого в REST вообще нет. Если балансирование нагрузки так важно - избегайте потоковых операций или ограничивайте их.


      1. mikegordan
        19.10.2023 16:24

        1) API GATEWAY :: не понял ту писанину без каких либо примеров. Они пытаются сказать используйте Google Cloud API , а мы внутри сами позаботимся о роутинге на нужный микросервис?

        2) load balanc: понятно что нету, но если ты запускаешь в кибернетусе микросервисы на REST , то он по /url запроса понимает в какому микросервису идет обращение и делает одну из указанных балансировок . Но через GRPC нету ничего подобного чтобы Кибернетус понял к какому микросервису идет запрос


  1. slonopotamus
    19.10.2023 16:24
    +6

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

    Я и безо всякого gRPC могу просто добавить новые экземпляры микросервисов.


    1. AlexSteelax
      19.10.2023 16:24
      +4

      Да ты просто не понимаешь: написано же "облегчает")))


  1. ndrwK
    19.10.2023 16:24

    Тема аутентификации не раскрыта.

    Что такое parseJWTFromContext в примере? Как токен попадает в контекст, и как потом оттуда достается?


  1. Akson87
    19.10.2023 16:24

    Хорошего о grpc написали, а о недостатках забыли. А там их не так уж и мало.

    Ну и хорошее оно больше про протобафы, ане про grpc. Никто не мешает пересылать протобафы в чистом http. А если вспомнить, что можно генерить json на основе протобавных схем когда надо, так все совсем становится интересно...

    Чем-то мне grpc напоминает k8s. Стильно модно молодежно, впихнем везде и всюду, а потом ой...