Как стать автором
Обновить
1863.32
Timeweb Cloud
То самое облако

Как создать микросервис на Rust при помощи gRPC

Уровень сложностиСложный
Время на прочтение6 мин
Количество просмотров7.3K
Автор оригинала: Engin Diri
image

Введение


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

Перед изучением этого поста также будет полезно посмотреть предыдущие публикации автора по Rust:

https://blog.ediri.io/lets-build-a-cli-in-rust
https://blog.ediri.io/how-to-asyncawait-in-rust-an-introduction

Предпосылки


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

  • Rust
  • IDE или текстовый редактор на ваш выбор
  • Компилятор буфера протоколов (protoc)

Установка protoc


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

Если вы работаете в macOS, то установку можно выполнить при помощи Homebrew:

brew install protobuf

Убедитесь, что компилятор protoc доступен в вашем пути PATH:

protoc --version # should print the version
# libprotoc 3.21.9

Теперь, когда мы всё обустроили, давайте немного обсудим вопрос: что такое gRPC 📡?

Что такое gRPC 📡?


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

Буферы протоколов


Буферы протоколов (Protocol Buffers) – это разработанный Google расширяемый механизм, нейтральный на уровне языка и платформы, предназначенный для сериализации структурированных данных. Эти буферы используются в gRPC по умолчанию.

Приведу пример, демонстрирующий, как работают Protocol Buffers. На первом этапе мы определяем структуру данных в файле с расширением .proto. Данные буфера протоколов структурированы в сообщениях, представляющих собой коллекции именованных полей. Вот очень упрощённый пример такого сообщения:

message Weather {
string city = 1;
int32 temperature = 2;
}

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

Можно определить gRPC-сервисы в том же .proto-файле, что и сообщения, с теми же RPC-методами, что используют эти сообщения.

service WeatherService {
  rpc GetWeather (WeatherRequest) returns (WeatherResponse) {}
}

message WeatherRequest {
  string city = 1;
}

message WeatherResponse {
  string forecast = 1;
}

Затем можно воспользоваться компилятором protoc, чтобы сгенерировать интерфейсы gRPC-клиента и сервера из .proto-сервиса.

Создаем микросервис на Rust


Создаём новый проект


Для начала создадим новый проект при помощи команды cargo:

cargo new

Добавляем поддержку CLI


Мы собираемся воспользоваться контейнером clap, чтобы добавить поддержку CLI к нашему микросервису и клиенту. Добавим в наш проект зависимость при помощи следующей команды:

cargo add clap --features derive

Создаём Proto-файл


Далее создадим новый каталог под названием proto, и в этом каталоге положим новый файл echo.proto. Далее определим наш сервис и те сообщения, которые собираемся использовать:

syntax = "proto3";
package api;

message EchoRequest {
  string message = 1;
}

message EchoResponse {
  string message = 1;
}

service EchoService {
  rpc Echo(EchoRequest) returns (EchoResponse);
}

Сгенерируем код Rust из Proto-файла


Чтобы сгенерировать код Rust из proto-файла, воспользуемся контейнером tonic-build. Нам потребуется добавить его в наш проект как зависимость сборки при помощи следующей команды:

cargo add tonic-build --build

Теперь можно добавить следующий код в наш файл build.rs:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    tonic_build::compile_protos("proto/echo.proto")?;
    Ok(())
}

Как правило, мы выполняем команду cargo build, чтобы сгенерировать код Rust из proto-файла. В IntelliJ IDEA вам, возможно, придётся активировать org.rust.cargo.evaluate.build.scripts в разделе настроек «Experimental Features» (Экспериментальные возможности), чтобы всё заработало.

tonic-build входит в состав контейнера tonic, который представляет собой реализацию gRPC поверх HTTP/2; эта реализация заточена на высокую производительность, интероперабельность и гибкость. В основе этой реализации лежат hyper, tokio и prost.

Вот некоторые возможности tonic:

  • Двунаправленная потоковая передача
  • Высокопроизводительный асинхронный ввод/вывод
  • Интероперабельность
  • TLS, поддерживаемая rustls
  • Балансировка нагрузки
  • Пользовательские метаданные
  • Аутентификация
  • Проверка работоспособности

Наконец, нам потребуется добавить tokio к нашему проекту в качестве зависимости:

cargo add tokio --features macros, rt-multi-thread

Теперь, когда сгенерирован весь код gRPC 📡, можно приступать к реализации нашего микросервиса.

Реализация микросервиса


Для начала создадим в каталоге src новый файл под названием server.rs.rs. Здесь мы собираемся реализовать нашу серверную логику.

Сначала нам потребуется импортировать сгенерированный код из нашего proto-файла, а также из контейнеров tonic и clap:
use tonic::{transport::Server, Request, Response, Status};

use api::echo_service_server::{EchoService, EchoServiceServer};
use api::{EchoRequest, EchoResponse};

use ::clap::{Parser};

Также понадобится включить сгенерированные из proto элементы для клиента и сервера, воспользовавшись для этого макросом include_proto!:

pub mod api {
    tonic::include_proto!("api");
}

Теперь можно реализовать сервисную логику нашего микросервиса. Мы собираемся реализовать метод Echo для нашего сервиса, и этот метод будет отзеркаливать то сообщение, которое мы отправили сервису. Здесь мы применяем ключевое слово async, чтобы функция стала асинхронной, а также #[tonic::async_trait], чтобы обеспечить совместимость с tonic.

#[derive(Debug, Default)]
pub struct Echo {}

#[tonic::async_trait]
impl EchoService for Echo {
    async fn echo(&self, request: Request<EchoRequest>) -> Result<Response<EchoResponse>, Status> {
        println!("Got a request: {:?}", request);

        let reply = EchoResponse {
            message: format!("{}", request.into_inner().message),
        };

        Ok(Response::new(reply))
    }
}

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

#[derive(Parser)]
#[command(author, version)]
#[command(about = "echo-server - a simple echo microservice", long_about = None)]
struct ServerCli {
    #[arg(short = 's', long = "server", default_value = "127.0.0.1")]
    server: String,
    #[arg(short = 'p', long = "port", default_value = "50052")]
    port: u16,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let cli = ServerCli::parse();
    let addr = format!("{}:{}", cli.server, cli.port).parse()?;
    let echo = Echo::default();

    println!("Server listening on {}", addr);

    Server::builder()
        .add_service(EchoServiceServer::new(echo))
        .serve(addr)
        .await?;

    Ok(())
}

Далее определим в файле Cargo.toml целевой бин:

[[bin]]
name = "echo-server"
path = "src/server.rs"

Запустим сервер:

cargo run --bin echo-server

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

Server listening on 127.0.0.1:50052


Можно сконфигурировать хост и порт сервера при помощи флагов --server и --port:

cargo run --bin echo-server -- --server 0.0.0.0 --port 50051


Реализация клиента


Для реализации клиента добавим следующие строки в имеющийся у нас файл main.rs. Сначала нужно импортировать сгенерированный код из нашего proto-файла и из контейнера clap, чтобы разобрать аргументы командной строки:

use api::echo_service_client::EchoServiceClient;
use api::EchoRequest;
use ::clap::{Parser};

pub mod api {
    tonic::include_proto!("api");
}

Как и в случае с сервером, воспользуемся clap, чтобы сконфигурировать хост и порт для нашего клиента. Здесь мы задействуем аргумент message, чтобы отправить на сервер выбранное нами сообщение:

#[derive(Parser)]
#[command(author, version)]
#[command(about = "echo - a simple CLI to send messages to a server", long_about = None)]
struct ClientCli {
    #[arg(short = 's', long = "server", default_value = "127.0.0.1")]
    server: String,
    #[arg(short = 'p', long = "port", default_value = "50052")]
    port: u16,
    /// The message to send
    message: String,
}

Нам осталось написать только главную функцию, которая будет создавать клиент и отправлять сообщение на сервер. Поскольку мы используем режим async/await, нам потребуется среда выполнения tokio. Для этого следует добавить атрибут #[tokio::main] к нашей главной функции:

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let cli = ClientCli::parse();

    let mut client = EchoServiceClient::connect(format!("http://{}:{}", cli.server, cli.port)).await?;

    let request = tonic::Request::new(EchoRequest {
        message: cli.message,
    });

    let response = client.echo(request).await?;

    println!("RESPONSE={:?}", response.into_inner().message);

    Ok(())
}

Также определим в нашем файле Cargo.toml целевой бин и для нашего клиента:

[[bin]]
name = "echo-client"
path = "src/main.rs"


Запустим клиент:

cargo run --bin echo-client -- "Hello World!"

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

RESPONSE="Hello World"

Сконфигурировать хост и порт для клиента можно при помощи флагов --server и --port, примерно как и в случае с сервером.

Заключение


В этой статье было рассказано, как при помощи tonic и clap написать простой gRPC-микросервис на Rust. Также мы узнали, как написать proto-файл и сгенерировать код для клиента и сервера при помощи tonic-build посредством build.rs

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

Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Всего голосов 26: ↑24 и ↓2+22
Комментарии18

Публикации

Информация

Сайт
timeweb.cloud
Дата регистрации
Дата основания
Численность
201–500 человек
Местоположение
Россия
Представитель
Timeweb Cloud