使用 Rust 構建 gRPC 微服務
前言
當前越來越多的公司基於 Google gRPC 通信框架來構建微服務體系,比較流行的是使用 Go/Java/C++ 這樣的主流編程語言來編寫服務端,我們今天來嘗試使用 Rust 來實現一個 gRPC 服務端 / 客戶端。
打開官方文檔可以看到目前 Rust 並不在 gRPC 官方支持的語言列表中:
Supported languages
• C#
• C++
• Dart
• Go
• Java
• Kotlin
• Node
• Objective-C
• PHP
• Python
• Ruby
不過不用擔心這個問題。我們知道只要某個語言兼容了基於 C/C++ 編寫的 gRPC 的核心庫,那麼該語言就可以完美支持 gRPC。目前 Rust 可以實現 gRPC 的主流 crate 如下:
-
• tonic
-
• grpc-rs
-
• grpc-rust
以上三種任選其一都可以,只是 grpc-rs/grpc-rust 當前還處於開發狀態,我們在這裏使用 tonic 包。
構建程序
首先檢查你的 Rust 版本:
$ rustc --version
rustc 1.61.0 (fe5b13d68 2022-05-18)
tonic 適用於 1.56 及以上,如果低於這個版本,你應該先更新你的 Rust 編譯器:
$ rustup update stable
確保你已經提前安裝了 protobuf:
$ protoc --version
libprotoc 3.19.4
# macOS 可以通過以下命令安裝
$ brew install protobuf
使用 cargo 新建一個項目
$ cargo new grpcrs
$ cd grpcrs
$ cargo run
Compiling grpcrs v0.1.0 (/Users/lvlv/Documents/project/demo/grpcrs)
Finished dev [unoptimized + debuginfo] target(s) in 0.55s
Running `target/debug/grpcrs`
Hello, world!
編輯 cargo.toml 文件:
[package]
name = "grpcrs"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "user-server"
path = "src/user/server.rs"
[[bin]]
name = "user-client"
path = "src/user/client.rs"
[dependencies]
tonic = "0.7.2"
tokio = { version = "1.18.2", features = ["macros", "rt-multi-thread"] }
prost = "0.10"
[build-dependencies]
tonic-build = "0.7.2"
創建下列文件:
$ mkdir -p proto/user src/user
$ touch build.rs proto/user/user.proto src/user/{server.rs,client.rs}
當前目錄結構:
$ tree -L 3
.
├── Cargo.lock
├── Cargo.toml
├── build.rs # Cargo 構建腳本
├── proto
│ └── user
│ └── user.proto # proto 文件
└── src
└── user
├── client.rs # gRPC 客戶端代碼
└── server.rs # gRPC 服務端代碼
分別將以下內容拷貝到各個文件:
- • proto/user/user.proto
syntax = "proto3";
package user;
service User {
rpc Hello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
- • src/user/server.rs
use tonic::{transport::Server, Request, Response, Status};
use user::user_server::{User, UserServer};
use user::{HelloReply, HelloRequest};
pub mod user {
tonic::include_proto!("user");
}
#[derive(Default)]
pub struct UserService {}
#[tonic::async_trait]
impl User for UserService {
async fn hello(&self, request: Request<HelloRequest>) -> Result<Response<HelloReply>, Status> {
println!("New user request from {:?}", request.remote_addr());
let reply = user::HelloReply {
message: format!("Hello {}!", request.into_inner().name),
};
Ok(Response::new(reply))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "127.0.0.1:50051".parse().unwrap();
let user_service = UserService::default();
println!("UserService listening on {}", addr);
Server::builder()
.add_service(UserServer::new(user_service))
.serve(addr)
.await?;
Ok(())
}
- • src/user/client.rs
use user::user_client::UserClient;
use user::HelloRequest;
pub mod user {
tonic::include_proto!("user");
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = UserClient::connect("http://127.0.0.1:50051").await?;
let request = tonic::Request::new(HelloRequest {
name: "Rick".into(),
});
let response = client.hello(request).await?;
println!("RESPONSE={:?}", response);
Ok(())
}
- • build.rs
use std::{env, path::PathBuf};
fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let user_proto = "proto/user/user.proto";
tonic_build::configure()
.build_server(true)
.build_client(true)
.out_dir(&out_dir)
.file_descriptor_set_path(&out_dir.join("user_descriptor.bin"))
.compile(&[user_proto], &["proto"])
.unwrap_or_else(|err| panic!("protobuf compile failed: {}", err));
}
嘗試編譯代碼:
$ cargo build
Compiling proc-macro2 v1.0.39
Compiling unicode-ident v1.0.0
Compiling syn v1.0.95
Compiling libc v0.2.126
Compiling cfg-if v1.0.0
Compiling log v0.4.17
# ... 省略
Compiling hyper v0.14.19
Compiling axum v0.5.6
Compiling hyper-timeout v0.4.1
Compiling tonic v0.7.2
Finished dev [unoptimized + debuginfo] target(s) in 21.01s
如果編譯通過,現在我們可以嘗試執行編譯好的程序了。
首先啓動 gRPC 服務端程序:
$ cargo run --bin user-server
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/user-server`
UserService listening on 127.0.0.1:50051
重新打開一個 terminal 窗口並執行客戶端程序:
$ cargo run --bin user-client
Finished dev [unoptimized + debuginfo] target(s) in 0.05s
Running `target/debug/user-client`
RESPONSE=Response { metadata: MetadataMap { headers: {"content-type": "application/grpc", "date": "Sun, 29 May 2022 21:54:18 GMT", "grpc-status": "0"} }, message: HelloReply { message: "Hello Rick!" }, extensions: Extensions } # <- 客戶端請求成功並返回響應
此時我們切回服務端 terminal 窗口查看日誌:
# ...
UserService listening on 127.0.0.1:50051
New user request from Some(127.0.0.1:52147) # <- 客戶端調用成功
至此,一個簡單的基於 Rust 的 gRPC 服務端 / 客戶端就實現了。上述代碼很簡陋,相信只要是接觸過 gRPC 的同學都比較容易就可以理解。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/daSNckS3Xfmx1azBjAR-vg