Rust高性能gRPC库ttrpc的使用,ttrpc提供轻量级RPC框架支持跨平台通信

ttrpc-rust 是 containerd 的非核心子项目。

ttrpc-rust 是 ttrpc 的 Rust 版本。ttrpc 是用于低内存环境的 GRPC。

ttrpc-rust 的 ttrpc 编译器 ttrpc_rust_plugin 是从 gRPC-rs 的 gRPC 编译器 grpcio-compiler 修改而来。

用法

  1. 使用 protoc 命令生成

从 proto 文件生成源代码:

  1. 安装 protoc

  2. 安装 protobuf-codegen

cargo install --force protobuf-codegen
  1. 安装 ttrpc_rust_plugin
cd ttrpc-rust/compiler
cargo install --force --path .
  1. 生成源代码:
$ protoc --rust_out=. --ttrpc_out=. --plugin=protoc-gen-ttrpc=`which ttrpc_rust_plugin` example.proto
  1. 以编程方式生成

用于生成 .rs 文件的 API,例如从 build.rs 中使用。

示例代码:

fn main() {
    protoc_rust_ttrpc::Codegen::new()
        .out_dir("protocols")
        .inputs(&[
            "protocols/protos/agent.proto",
        ])
        .include("protocols/protos")
        .rust_protobuf() // 同时生成 protobuf 消息,不仅仅是服务
        .run()
        .expect("Codegen failed.");
}

async/.await

ttrpc-rust 支持 async/.await。通过使用 async/.await,可以减少由线程引起的开销和资源消耗。

用法

  1. 生成异步版本的代码

目前我们仅支持使用 ttrpc-codegen 生成异步代码

    ttrpc_codegen::Codegen::new()
        .out_dir("protocols/asynchronous")
        .inputs(&protos)
        .include("protocols/protos")
        .rust_protobuf()
        .customize(Customize {
            gen_mod: true, // Gen mod 将在 out_dir 中添加 mod.rs。它与 protobuf 的 gen_mod_rs 兼容
            async_all: true, // 这是关键选项。
            ..Default::default()
        })
        .run()
        .expect("Gen async codes failed.");

提供自定义选项

async_all: 为服务器和客户端生成异步代码 async_server: 为服务器生成异步代码 async_client: 为客户端生成异步代码 gen_mod: 在 out_dir 中生成 mod.rs

请参阅 example/build.rs 中的更多内容

  1. 以 async/.await 的方式编写您的实现

请遵循 example/async-server.rs 和 example/async-client.rs 中的指南

运行示例

  1. 进入目录
$ cd ttrpc-rust/example
  1. 启动服务器
$ cargo run --example server

$ cargo run --example async-server
  1. 启动客户端
$ cargo run --example client

$ cargo run --example async-client

注意:protobuf 的版本

protobuf-codegen、ttrpc_rust_plugin 和您的代码应使用相同版本的 protobuf。 如果使用不同版本的 protobuf,将出现以下错误:

27 | const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_8_0;
   |                                                 ^^^^^^^^^^^^^ help: a constant with a similar name exists: `VERSION_2_10_1`

原因是 protobuf-codegen 生成的文件仅与相同版本的运行时兼容

要解决此问题:

  1. 使用新的 protobuf 重新构建 protobuf-codegen:
cd grpc-rs
cargo clean
cargo update
cargo install --force protobuf-codegen
  1. 使用新的 protobuf 重新构建 ttrpc_rust_plugin:
cd ttrpc-rust/compiler
cargo clean
cargo update
cargo install --force --path .
  1. 构建您的项目。

完整示例代码:

// build.rs
fn main() {
    // 生成同步版本代码
    protoc_rust_ttrpc::Codegen::new()
        .out_dir("src/protocols")
        .inputs(&["protos/example.proto"])
        .include("protos")
        .rust_protobuf()
        .run()
        .expect("Codegen failed.");

    // 生成异步版本代码
    ttrpc_codegen::Codegen::new()
        .out_dir("src/protocols_async")
        .inputs(&["protos/example.proto"])
        .include("protos")
        .rust_protobuf()
        .customize(ttrpc_codegen::Customize {
            gen_mod: true,
            async_all: true,
            ..Default::default()
        })
        .run()
        .expect("Gen async codes failed.");
}

// src/main.rs
mod protocols;
mod protocols_async;

use ttrpc::context;

fn main() {
    // 同步客户端示例
    let client = protocols::example::ExampleClient::new("unix:///tmp/example.sock").unwrap();
    let ctx = context::Context::new();
    let request = protocols::example::Request::new();
    let response = client.echo(ctx, &request).unwrap();
    println!("Response: {:?}", response);

    // 异步客户端示例
    let rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(async {
        let client = protocols_async::example::ExampleClient::new("unix:///tmp/example.sock").unwrap();
        let ctx = context::Context::new();
        let request = protocols_async::example::Request::new();
        let response = client.echo(ctx, &request).await.unwrap();
        println!("Async response: {:?}", response);
    });
}

// 服务器实现示例
struct ExampleService;

impl protocols::example::Example for ExampleService {
    fn echo(&self, _ctx: &ttrpc::context::Context, req: &protocols::example::Request) 
        -> ttrpc::Result<protocols::example::Response> 
    {
        let mut resp = protocols::example::Response::new();
        resp.set_message(req.get_message().to_string());
        Ok(resp)
    }
}

// 异步服务器实现示例
struct AsyncExampleService;

#[async_trait::async_trait]
impl protocols_async::example::Example for AsyncExampleService {
    async fn echo(&self, _ctx: &ttrpc::context::Context, req: &protocols_async::example::Request) 
        -> ttrpc::Result<protocols_async::example::Response> 
    {
        let mut resp = protocols_async::example::Response::new();
        resp.set_message(req.get_message().to_string());
        Ok(resp)
    }
}

1 回复

ttrpc:Rust高性能gRPC库使用指南

概述

ttrpc是一个基于Rust语言开发的高性能gRPC库,提供轻量级RPC框架,支持跨平台通信。该库专为需要高效网络通信的场景设计,具有低延迟、高吞吐量的特点,同时保持代码简洁和易于使用。

主要特性

  • 高性能异步通信
  • 跨平台支持(Linux、Windows、macOS)
  • Protocol Buffers协议支持
  • 轻量级设计,依赖较少
  • 完整的gRPC功能实现

安装方法

在Cargo.toml中添加依赖:

[dependencies]
ttrpc = "0.6"
prost = "0.11"
tokio = { version = "1.0", features = ["full"] }

基本使用方法

1. 定义Proto文件

首先创建proto文件定义服务:

syntax = "proto3";

package example;

service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}

2. 生成Rust代码

使用prost-build生成Rust代码:

// build.rs
fn main() -> Result<(), Box<dyn std::error::Error>> {
    prost_build::compile_protos(&["src/example.proto"], &["src/"])?;
    Ok(())
}

3. 实现服务端

use ttrpc::{self, asynchronous::Server};
use example::{Greeter, HelloRequest, HelloReply};
use async_trait::async_trait;

#[derive(Clone)]
struct GreeterService;

#[async_trait]
impl Greeter for GreeterService {
    async fn say_hello(
        &self,
        _ctx: &ttrpc::r#async::Context,
        req: HelloRequest,
    ) -> ttrpc::Result<HelloReply> {
        let message = format!("Hello, {}!", req.name);
        Ok(HelloReply { message })
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let greeter = GreeterService;
    let service = example::create_greeter(greeter);
    
    let mut server = Server::new()
        .bind("unix:///tmp/ttrpc_example.sock")?
        .register_service(service);
    
    server.start().await?;
    Ok(())
}

4. 实现客户端

use ttrpc::{self, asynchronous::Client};
use example::{GreeterClient, HelloRequest};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::connect("unix:///tmp/ttrpc_example.sock")?;
    let greeter_client = GreeterClient::new(client);
    
    let request = HelloRequest {
        name: "World".to_string(),
    };
    
    let response = greeter_client.say_hello(ttrpc::context::empty(), &request).await?;
    println!("Response: {}", response.message);
    
    Ok(())
}

高级配置

自定义服务器选项

use ttrpc::asynchronous::Server;
use ttrpc::ServerOption;

let options = ServerOption {
    work_threads: 4,
    ..Default::default()
};

let server = Server::with_option(options)
    .bind("tcp://0.0.0.0:50051")?
    .register_service(service);

错误处理示例

async fn say_hello(
    &self,
    _ctx: &ttrpc::r#async::Context,
    req: HelloRequest,
) -> ttrpc::Result<HelloReply> {
    if req.name.is_empty() {
        return Err(ttrpc::Error::RpcStatus(ttrpc::Status::new(
            ttrpc::Code::INVALID_ARGUMENT,
            "Name cannot be empty",
        )));
    }
    
    Ok(HelloReply {
        message: format!("Hello, {}!", req.name),
    })
}

性能优化建议

  1. 使用合适的线程池大小
  2. 启用TCP_NODELAY减少延迟
  3. 使用连接池管理客户端连接
  4. 合理设置消息大小限制

注意事项

  • 确保proto文件定义与生成的Rust代码保持一致
  • 在生产环境中使用适当的错误处理和日志记录
  • 考虑使用TLS加密进行安全通信
  • 监控服务器资源使用情况

ttrpc为Rust开发者提供了强大而高效的gRPC实现,特别适合需要高性能网络通信的应用程序。

完整示例demo

以下是一个完整的ttrpc使用示例,包含服务端和客户端的完整实现:

项目结构:

ttrpc-example/
├── Cargo.toml
├── build.rs
├── src/
│   ├── main.rs
│   └── example.proto

Cargo.toml:

[package]
name = "ttrpc-example"
version = "0.1.0"
edition = "2021"

[dependencies]
ttrpc = "0.6"
prost = "0.11"
tokio = { version = "1.0", features = ["full"] }
async-trait = "0.1"
prost-types = "0.11"

[build-dependencies]
prost-build = "0.11"

build.rs

// 构建脚本,用于从proto文件生成Rust代码
fn main() -> Result<(), Box<dyn std::error::Error>> {
    prost_build::compile_protos(&["src/example.proto"], &["src/"])?;
    Ok(())
}

src/example.proto:

syntax = "proto3";

package example;

// 定义问候服务
service Greeter {
    // 打招呼方法
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// 请求消息
message HelloRequest {
    string name = 1;  // 姓名字段
}

// 响应消息
message HelloReply {
    string message = 1;  // 消息字段
}

src/main.rs:

// 生成proto文件对应的Rust代码
mod example {
    include!(concat!(env!("OUT_DIR"), "/example.rs"));
}

use ttrpc::{self, asynchronous::{Server, Client}};
use example::{Greeter, HelloRequest, HelloReply, GreeterClient, create_greeter};
use async_trait::async_trait;
use std::error::Error;

// 服务实现结构体
#[derive(Clone)]
struct GreeterService;

#[async_trait]
impl Greeter for GreeterService {
    // 实现say_hello方法
    async fn say_hello(
        &self,
        _ctx: &ttrpc::r#async::Context,
        req: HelloRequest,
    ) -> ttrpc::Result<HelloReply> {
        // 检查名称是否为空
        if req.name.is_empty() {
            return Err(ttrpc::Error::RpcStatus(ttrpc::Status::new(
                ttrpc::Code::INVALID_ARGUMENT,
                "Name cannot be empty",
            )));
        }
        
        // 构造响应消息
        let message = format!("Hello, {}!", req.name);
        Ok(HelloReply { message })
    }
}

// 服务端主函数
#[tokio::main]
async fn server_main() -> Result<(), Box<dyn Error>> {
    println!("Starting ttrpc server...");
    
    // 创建服务实例
    let greeter = GreeterService;
    let service = create_greeter(greeter);
    
    // 配置服务器选项
    let options = ttrpc::ServerOption {
        work_threads: 4,
        ..Default::default()
    };
    
    // 创建并启动服务器
    let mut server = Server::with_option(options)
        .bind("unix:///tmp/ttrpc_example.sock")?
        .register_service(service);
    
    println!("Server listening on unix:///tmp/ttrpc_example.sock");
    server.start().await?;
    
    Ok(())
}

// 客户端主函数
#[tokio::main]
async fn client_main() -> Result<(), Box<dyn Error>> {
    println!("Starting ttrpc client...");
    
    // 连接服务器
    let client = Client::connect("unix:///tmp/ttrpc_example.sock")?;
    let greeter_client = GreeterClient::new(client);
    
    // 创建请求
    let request = HelloRequest {
        name: "World".to_string(),
    };
    
    // 发送请求并获取响应
    let response = greeter_client
        .say_hello(ttrpc::context::empty(), &request)
        .await?;
    
    println!("Server response: {}", response.message);
    
    // 测试错误情况
    let empty_request = HelloRequest {
        name: "".to_string(),
    };
    
    match greeter_client
        .say_hello(ttrpc::context::empty(), &empty_request)
        .await
    {
        Ok(_) => println!("Unexpected success"),
        Err(e) => println!("Expected error: {}", e),
    }
    
    Ok(())
}

// 主函数,可以选择运行服务端或客户端
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let args: Vec<String> = std::env::args().collect();
    
    if args.len() > 1 && args[1] == "server" {
        server_main().await
    } else {
        client_main().await
    }
}

运行说明:

  1. 首先启动服务端:
cargo run -- server
  1. 然后在另一个终端运行客户端:
cargo run

这个完整示例展示了ttrpc的基本使用方法,包括:

  • Proto文件的定义和代码生成
  • 服务端的实现和配置
  • 客户端的连接和调用
  • 错误处理机制
  • 异步通信的实现

通过这个示例,您可以快速开始使用ttrpc构建高性能的gRPC服务。

回到顶部