Rust でも gRPC が使いたい！

[Rust 1.39.0](https://blog.rust-lang.org/2019/11/07/Rust-1.39.0.html) で`async/.await`が安定化され、今後はサーバーサイドの用途で Rust が採用されるケースが増えそうですね（期待を込めて）。  
そのなかでも今回は、マイクロサービス指向で作成されたプロジェジェクトで採用されることが多い gRPC にフォーカスし、Rust の gRPC ライブラリである [hyperium/tonic](https://github.com/hyperium/tonic) について紹介します。

<img src="https://raw.githubusercontent.com/hyperium/tonic/master/.github/assets/tonic-banner.svg?sanitize=true" width="800px"/>

## Rust の gRPC ライブラリ

`Tonic`のことを紹介する前に、他の gRPC ライブラリについても軽く触れておきます。

2019 年 12 月時点で、[`grpc-ecosystem/awesome-grpc`](https://github.com/grpc-ecosystem/awesome-grpc)リポジトリには以下のライブラリが紹介されています。

- [stepancheg/grpc-rust](https://github.com/stepancheg/grpc-rust) - Rust implementation of gRPC
- [tikv/grpc-rs](https://github.com/tikv/grpc-rs) - The gRPC library for Rust built on C Core library and futures
- [tower-rs/tower-grpc](https://github.com/tower-rs/tower-grpc) - A client and server gRPC implementation based on Tower
- [hyperium/tonic](https://github.com/hyperium/tonic) - A native gRPC client & server implementation with async/await support

もっとも古い `stepancheg/grpc-rust` は 2015 年より開発が始まっています。私も長い間リポジトリをウォッチしていましたが、 継続的に開発が進められているものの、個人プロジェクトということもあり、リリース自体は 2018 年で止まっています。  
`tikv/grpc-rs` は、PingCAP 社が開発し、[Bay Area Rust Meetup August 2017](https://air.mozilla.org/bay-area-rust-meetup-august-2017/) で事例紹介がされたことから、早期の段階で本番環境に導入されたライブラリではないでしょうか。  
Web framework である [tower-rs/tower](https://github.com/tower-rs/tower) をベースにして作られた`tower-rs/tower-grpc`は、Tonic の前身となるプロジェクトです。

そして今回紹介する`hyperium/tonic`は、2019 年 8 月から開発が始まった、もっとも新しいプロジェクトで、 2019 年 12 月 15 日時点では、**[アルファ版のみのリリース](https://github.com/hyperium/tonic/releases)となります**。

## Tonic とは

Tonic を 1 行で説明すると、ハイパフォーマンスで、相互運用性と柔軟性にフォーカスした gRPC over HTTP/2 のライブラリとなります。

背景を交えた説明は、メイン開発者の一人である [Timber.io](https://timber.io/) の Lucio Franco さんのブログ [Tonic: gRPC has come to async/await!](https://luciofran.co/tonic-grpc-has-come-to-async-await/)に記載されていたので、そちらのブログから一部抜粋して紹介します。  

もともと Tonic が開発される前には、前進のプロジェクトである`tower-grpc`がありました。  
`tower-grpc`は`futures 0.1`をベースにして開発され、本番環境でも導入できる gRPC ライブラリとして [CNCF](https://www.cncf.io/) 傘下のプロジェクトである Service Mesh の [Linkerd](https://linkerd.io/) のニーズから始まりました。

実際に Linkerd のコンポーネントである[`linker-proxy`](https://github.com/linkerd/linkerd2-proxy)の中で`tower-grpc`は使用されており、Linkerd 自体も[Kubecon NA 2019](https://linkerd.io/2019/11/26/linkerd-at-kubecon-na-2019-roundup/)で、いくつかのセッションで事例が紹介されるなど、本番環境での採用も増えてきたライブラリとなります。

そして、`async/await`の安定化により、この新しい構文をファーストクラスでサポートするために Tonic が作成されました。  
Tonic には gRPC の実装だけでなく、hyper、tokio、tower などのライブラリを利用して HTTP/2 のクライアントとサーバーの実装も含まれています。

その他にも 相互運用性を保つため、コミット ごとに grpc-go の実装に対して `gRPC interop test cases` のチェックを実施しています。

予定のものを含め Tonic で提供する機能は以下のとおりとなります。

- Implemented in pure Rust (minus openssl which is optional)
- Interoperability tested via [tonic-interop](https://github.com/hyperium/tonic/tree/master/interop)
- Bi-directional streaming
- Custom metadata
- Trailing metadata
- Codegen via [prost](https://github.com/danburkert/prost)
- Exposes [tracing](https://github.com/tokio-rs/tracing) diagnostics
- Fully featured HTTP/2 client and server based on hyper
- TLS backed by either openssl or rustls
- Load balancing powered by tower
- Reliability features such as timeouts, rate limiting, concurrency control, and more
- gRPC interceptors

サービスメッシュのライブラリとして利用されることを意識した機能も多いですね。

## 基本的な使い方

基本的な使い方は gRPC を利用された経験があれば、それほど難しくはありません。

Tonic の場合、plugin を利用して直接 protoc コマンドを呼び出すのではなく、prost-build に コード生成の処理を委譲し、prost-build の処理の中で protoc コマンドが実行されます。
またこの prost-build には protoc コマンドがバイナリとして埋め込まれているため、protoc コマンドのインストールも不要です。  

最新の protoc バイナリや、特定バージョンのバイナリを利用したい場合でも、`PROTOC`環境変数で利用するバイナリを上書きできる仕組みが用意されているようなので、複数言語で共通の protoc を使いたい場合でも問題なさそうです。

注意点としては prost-build で生成されたコードにいくつかの依存ライブラリがあるため、いくつかの依存ライブラリの追加が必要となります。
他にも生成されたコードは、デフォルトでは target ディレクトリ配下に作成されるため IDE などでコード補完ができない問題が発生します。
この問題は生成されたコードの出力先を変更することで解決できるかもしれませんが、[cargo publish が出来ない](https://doc.rust-lang.org/cargo/reference/build-scripts.html#case-study-code-generation)などの問題があるため、target ディレクトリ以外に生成先を変更するのは避けたほうが無難です。


それでは実際にプロジェクトを作成して動作を確認していきます。

```
# サンプルプロジェクトを作成
 ❯❯ cargo new --bin rust-grpc-web-server && cd rust-grpc-web-server

# helloworld の proto ファイルをダウンロード
 ❯❯ mkdir proto
 ❯❯ wget https://raw.githubusercontent.com/grpc/grpc/master/examples/protos/helloworld.proto -O ./proto/helloworld.proto
```

次に Tonic と prost-build で生成された Rust のコードに必要な依存ライブラリを追加します。  
また、ここでビルドスクリプトに必要な tonic-build の依存も合わせて追加します。

```Cargo.tml
...
[dependencies]
tonic        = "0.1.0-alpha.6"
bytes        = "0.4"
prost        = "0.5"
prost-derive = "0.5"
tokio        = "0.2.0-alpha.6"

[build-dependencies]
tonic-build  = "0.1.0-alpha.6"
```

ビルドスクリプトを追加し、ビルドごとに proto ファイルから Rust コードが生成されるようにします。

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

最後に Tonic と非同期処理の Runtime として tokio を使い、helloworld を実装します。

```rust:src/main.rs
use tonic::{transport::Server, Request, Response, Status};

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

use hello_world::{
    server::{Greeter, GreeterServer},
    HelloReply, HelloRequest,
};

#[derive(Default)]
pub struct MyGreeter {}

#[tonic::async_trait]
impl Greeter for MyGreeter {
    async fn say_hello(&self, request: Request<HelloRequest>) -> Result<Response<HelloReply>, Status> {
        let reply = hello_world::HelloReply {
            message: format!("Hello {}!", request.into_inner().name).into(),
        };
        Ok(Response::new(reply))
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "[::1]:50051".parse().unwrap();
    let greeter = MyGreeter::default();

    Server::builder()
        .add_service(GreeterServer::new(greeter))
        .serve(addr)
        .await?;

    Ok(())
}
```

このコードをビルドすると、以下のパスに helloworld.rs が生成されます。

```
 ❯❯ cargo build

 ❯❯ ls -al target/debug/build/rust-grpc-web-server-xxxxx/out/helloworld.rs
 .rw-r--r-- 6.0k watawuwu  1 Dec 16:57 target/debug/build/rust-grpc-web-server-xxxx/out/helloworld.rs
```

``` rust:helloworld.rs
/// The request message containing the user's name.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct HelloRequest {
    #[prost(string, tag = "1")]
    pub name: std::string::String,
}
/// The response message containing the greetings
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct HelloReply {
    #[prost(string, tag = "1")]
    pub message: std::string::String,
}
...

```

それでは grpc のクライアントの [grpcurl](https://github.com/fullstorydev/grpcurl) を使って、通信の確認をします。

```
# サーバーを起動
 ❯❯ cargo run
  Finished dev [unoptimized + debuginfo] target(s) in 47.46s
  Running `target/debug/rust-grpc-web-server`

# 別コンソールから実行
 ❯❯ grpcurl \
  -proto proto/helloworld.proto \
  -d '{"name":"watawuwu"}' \
  -plaintext \
  localhost:50051 \
  helloworld.Greeter.SayHello
{
  "message": "Hello watawuwu!"
}
```

バッチリ通信できました :rocket:

## パフォーマンス比較

この helloworld の実装を使って、性能比較をしたいと思います。  
環境は手持ちの MacBook Pro で実行し、パラメーターチューニングも実行しないため、参考程度の情報として扱ってください。

また比較用として、`stepancheg/grpc-rust` と`tikv/grpc-rs`でも同じような helloworld の実装を用意しました。  
[ソースコードはこちら](https://github.com/watawuwu/rust-grpc-web-server)で確認できます。

負荷クライアントには [ghz](https://github.com/bojand/ghz) を利用し、結果は以下のとおりとなりました。

|                                | Requests/sec | Average(ms) | 99% tile(ms) |
|--------------------------------|--------------|-------------|--------------|
| stepancheg/grpc-rust(v0.6.1)   | 18475        | 5.35        | 9.3          |
| stepancheg/grpc-rust(latest)   | 45352        | 2.15        | 3.93         |
| tikv/grpc-rs(v0.4.6)           | 30950        | 1.25        | 2.32         |
| hyperium/tonic(v0.1.0-alpha.6) | 51898        | 1.81        | 4.12         |

Alpha 版にもかかわらず Tonic がパフォーマンスでは健闘しています。

全ての出力結果は[こちら](https://github.com/watawuwu/rust-grpc-web-server/blob/master/report.md)から参照できます。

## Load Balancing

実は、機能一覧にあったこの`Load Balancing`がかなり気になっていました。  
というのも gRPC で[設計](https://github.com/grpc/grpc/blob/master/doc/load-balancing.md#balancing-aware-client)されている`Balancing-aware Client`が実装されていると、プロキシを別途用意しなくてもクライアント側でバランシングできるのでとても便利です。小規模アプリケーションでは特に活躍する機能です。

ですが実装を見たところ、Load Balancing 機能は実装されていましたが、サーバーリストを動的に取得するような、DNS リゾルバの機能が提供されていませんでした :cry:  
[Discord](https://discordapp.com/channels/500028886025895936/627696030334582784) を見ると [trust-dns](https://github.com/bluejekyll/trust-dns) の作者に実装方法について質問していたので、もしかしたらリゾルバ実装の候補を探しているかもしれません。

## 今後の動向

最後に、絶賛開発中である Tonic の今後の動向を知るために、気になる Issue について紹介して終わりたいと思います。
（開発が活発なので、記事の公開時に解決されているIssueも多いかもしれません）

### [Support futures runtimes other than Tokio(#152)](https://github.com/hyperium/tonic/issues/152)
futures runtime として `async-std` などのサポートについて議論されている Issue です。

今の開発ステータスは Alpha バージョンであり、まずはすぐに使えるものが選択されたようです。  
特に HTTP2 実装は hyper/h2 以外に実用的なものがなく、それらに依存したライブラリ群となっているようです。 

現状のネットワーク系のライブラリは trust-dns を含め、tokio に依存しているものが多いので、当面の間は tokio runtime のサポートだけになりそうです。

### [Upgrade to `tokio` 0.2(#159)](https://github.com/hyperium/tonic/issues/159)
~~tokio 0.2 へのアップグレードは、hyper 0.13 のリリースと、いくつかの tower ライブラリの対応待ちです。~~
=> 対応されてました https://github.com/hyperium/tonic/pull/163

### [Graceful shutdown docs/example/default-impl(#144)](https://github.com/hyperium/tonic/issues/144)
~~実際の挙動で確認できていませんが、現状だと gRPC の[health checking service(#135)](https://github.com/hyperium/tonic/issues/135) に対応できていないため、Graceful shutdown に対応できていないようです。~~
=> 対応されてました https://github.com/hyperium/tonic/pull/169

### [Upstream to main gRPC repo(#149)](https://github.com/hyperium/tonic/issues/149)
本家 gPRC リポジトリでの管理についての Issue です。  

本家側での [Issue](https://github.com/grpc/grpc/issues/9316#issuecomment-273529285) でもやりとりされていますが、どのような基準で本家 gRPC 管理に採択されるのでしょうか。
どの gRPC リポジトリにせよ、本家で管理されるようになれば Rust でも gRPC 利用者の増加を見込めますしメリットも多いのかもしれません。  
でも取りあえずは 0.1.0 リリースに向けて頑張ってほしいですね！