Volo - Rust gRPC 框架实战

一、参考资料

Volo-GitHub

Volo-Overview

二、开发环境搭建

1、安装脚手架

# 安装 volo-cli
cargo install volo-cli
# 验证安装
volo help

2、编写 IDL 

# 文件 volo_demo.proto

syntax = "proto3";
package volo.demo;

message Item {
    int64 id = 1;
    string title = 2;
    string content = 3;

    map<string, string> extra = 10;
}

message GetItemRequest {
    int64 id = 1;
}

message GetItemResponse {
    Item item = 1;
}

service ItemService {
    rpc GetItem(GetItemRequest) returns (GetItemResponse);
}

IDL 全称为 Interface Definition Language ,即为 接口定义语言,具体语法参考 proto3。

为什么需要 IDL ?首先我们搞明白 RPC 是个什么概念:Remote Produce Call ,远程过程调用。说白了就是 A 机器从直接调用 B 机器上的某个函数或者方法。例如我在 A 机器用 Java 语言写了一个 Add 函数,返回变量加一后的结果,B 机器的 Go 语言程序想直接调用 A 机器的 Add 函数,就像在调用 B 机器本身程序内的函数一样。因此,需要引入 IDL 来定义这样一套接口标准,让 A、B 机器实现这种交互,就算开发语言不同,也能清楚对应调用哪个函数、什么类型的参数。

3、使用脚手架初始化项目

# 初始化项目,并生成模板代码
volo init --includes=idl volo-demo idl/volo_demo.proto

# 如果只需要增加一个 IDL(如 client 的 IDL)而不需要生成模板
volo idl add idl/volo_example.proto
#初始化项目后,项目根文件夹下多出以下内容
$ ls
Cargo.lock  Cargo.toml  idl/  rust-toolchain.toml  src/  target/  volo-gen/

三、实现一个 gRPC Server

1、在 src/lib.rs 下实现一个 get_item 方法。

#![feature(type_alias_impl_trait)]

pub struct S;

#[volo::async_trait]
impl volo_gen::volo::demo::ItemService for S {
    // 这部分是我们需要增加的代码
    async fn get_item(
        &self,
        _req: volo_grpc::Request<volo_gen::volo::demo::GetItemRequest>,
    ) -> core::result::Result<volo_grpc::Response<volo_gen::volo::demo::GetItemResponse>, volo_grpc::Status>
    {
        Ok(volo_grpc::Response::new(Default::default()))
    }
}

 2、然后执行指令编译二进制程序,生成指定的 volo_gen.rs 文件::

cargo update
cargo build

3、此时,我们就可以执行指令把 server 运行起来 :

cargo run --bin server

可以看到 Server 已经成功运行起来,这里用到了 tracing 日志监控输出,后续再详细介绍。

四、实现一个 Client 

1、新增依赖

[package]
name = "volo_demo"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1"
async-trait = "0.1"
lazy_static = "1"
tokio = { version = "1", features = ["full"] }
tracing = "0.1"
prost = "0.11"

pilota = "*"      # we recommend to use the latest framework version for new features and bug fixes
volo = "*"        # we recommend to use the latest framework version for new features and bug fixes
volo-grpc = "*"   # we recommend to use the latest framework version for new features and bug fixes

volo-gen = { path = "./volo-gen" }

[profile.release]
opt-level = 3
debug = true
debug-assertions = false
overflow-checks = false
lto = true
panic = 'unwind'
incremental = false
codegen-units = 1
rpath = false

[workspace]
members = ["volo-gen"]
resolver = "2"

2、新增 client.rs 

use lazy_static::lazy_static;
use std::net::SocketAddr;

lazy_static! {
    static ref CLIENT: volo_gen::volo::demo::ItemServiceClient = {
        let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
        volo_gen::volo::demo::ItemServiceClientBuilder::new("volo_demo")
            .address(addr)
            .build()
    };
}

#[volo::main]
async fn main() {
    let req = volo_gen::volo::demo::GetItemRequest { id: 1024 };
    let resp = CLIENT.clone().get_item(req).await;
    match resp {
        Ok(info) => tracing::info!("{:?}", info),
        Err(e) => tracing::error!("{:?}", e),
    }
}

 3、Client 请求 Server 测试

cargo run --bin server
cargo run --bin client

可以看到已经成功请求。 

五、实现一个日志中间件 

1、新增日志依赖

tracing = "0.1"
tracing-subscriber = "0.3"

2、在 lib.rs 中新增中间件服务

#![feature(type_alias_impl_trait)]

pub struct S;

#[volo::async_trait]
impl volo_gen::volo::demo::ItemService for S {
    // 这部分是我们需要增加的代码
    async fn get_item(
        &self,
        _req: volo_grpc::Request<volo_gen::volo::demo::GetItemRequest>,
    ) -> core::result::Result<volo_grpc::Response<volo_gen::volo::demo::GetItemResponse>, volo_grpc::Status>
    {
        Ok(volo_grpc::Response::new(Default::default()))
    }
}

// 中间件服务,打印出我们收到的请求、返回的响应以及消耗的时间
#[derive(Clone)]
pub struct LogService<S>(S);

#[volo::service]
impl<Cx, Req, S> volo::Service<Cx, Req> for LogService<S>
where
    Req: Send + 'static,
    S: Send + 'static + volo::Service<Cx, Req>,
    Cx: Send + 'static,
{
    async fn call(&mut self, cx: &mut Cx, req: Req) -> Result<S::Response, S::Error> {
        let now = std::time::Instant::now();
        let resp = self.0.call(cx, req).await;
        tracing::info!("Request took {}ms", now.elapsed().as_millis());
        resp
    }
}

// 我们给这个 Service 包装一层 Layer ,便于 server 、 client 调用
pub struct LogLayer;

impl<S> volo::Layer<S> for LogLayer {
    type Service = LogService<S>;

    fn layer(self, inner: S) -> Self::Service {
        LogService(inner)
    }
}

3、在 Server/Client 中加入日志中间件

use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt};

// 只有注册 subscriber 后, 才能在控制台上看到日志输出
tracing_subscriber::registry().with(fmt::layer()).init();


// client

use lazy_static::lazy_static;
use std::net::SocketAddr;
use volo_demo::LogLayer;
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt};

lazy_static! {
    static ref CLIENT: volo_gen::volo::demo::ItemServiceClient = {
        let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
        volo_gen::volo::demo::ItemServiceClientBuilder::new("volo_demo")
            .layer_inner(LogLayer)
            .address(addr)
            .build()
    };
}

#[volo::main]
async fn main() {

    // 只有注册 subscriber 后, 才能在控制台上看到日志输出
    tracing_subscriber::registry().with(fmt::layer()).init();
    let req = volo_gen::volo::demo::GetItemRequest { id: 1024 };
    let resp = CLIENT.clone().get_item(req).await;
    match resp {
        Ok(info) => tracing::info!("{:?}", info),
        Err(e) => tracing::error!("{:?}", e),
    }
}

 4、自定义函数返回


#[volo::async_trait]
impl volo_gen::volo::demo::ItemService for S {
    // 这部分是我们需要增加的代码
    async fn get_item(
        &self,
        _req: volo_grpc::Request<volo_gen::volo::demo::GetItemRequest>,
    ) -> core::result::Result<
        volo_grpc::Response<volo_gen::volo::demo::GetItemResponse>,
        volo_grpc::Status,
    > {
        // 默认返回空
        // Ok(volo_grpc::Response::new(Default::default()))

        // 返回自定义数据
        Ok(volo_grpc::Response::new(
            volo_gen::volo::demo::GetItemResponse {
                item: Some(volo_gen::volo::demo::Item {
                    id: 1024,
                    title: "hello".to_string(),
                    content: "just for test.".to_string(),
                    extra: Default::default(),
                }),
            },
        ))
    }
}

 5、测试

可以看到 gRPC 调用成功,并且日志输出了自定义的返回值! 

六、跨语言通讯

Client : Rust 语言

Server : C# 语言

1、公共 IDL

syntax = "proto3";

option csharp_namespace = "PrinterGrpcService";

package greet;

// The greeting 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;
}

2、Client 端

按照上述流程,编写 client.rs 

use lazy_static::lazy_static;
use std::net::SocketAddr;
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt};
use myapp::libs::my_tracing::LogLayer;

lazy_static! {
    static ref CLIENT: volo_gen::greet::GreeterClient = {
        let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
        volo_gen::greet::GreeterClientBuilder::new("greet-client")
            .layer_inner(LogLayer)
            .address(addr)
            .build()
    };
}

#[volo::main]
async fn main() {
    // 只有注册 subscriber 后, 才能在控制台上看到日志输出
    tracing_subscriber::registry().with(fmt::layer()).init();
    let req = volo_gen::greet::HelloRequest { name: "yushanma".to_string() };
    let resp = CLIENT.clone().say_hello(req).await;
    match resp {
        Ok(info) => tracing::info!("{:?}", info),
        Err(e) => tracing::error!("{:?}", e),
    }
}

2、Server 端

使用 .Net Core 框架创建模板

生成的代码文件跟 volo 如出一辙:定义 IDL,定义服务函数,启动 Server 监听请求。

启动服务端后再启动客户端测试:请求成功!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

余衫马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值