こんにちは、やまなかです。
今回はサーバー上の関数を実行するためのプロトコル(通信規格)であるRPC (Remote Procedure Call) を実現するため、Googleが開発したgRPCについてまとめていきます。
はじめにRPCとは?
以下のコードのように、ローカル上でmain関数からhello関数を呼び出すように、リモートサーバ上の関数を呼び出して応答を受け取ることを目的としたAPIアーキテクチャです。サーバ側やクライアント側が異なるフォーマットを使っていても、ルールを定義することでそれぞれに合わせたフォーマットで結果を受け取ることができる点がポイントです。メソッドを呼び出すときは、関数を呼び出すときのように引数をクエリ文字列に含めて送信します。
#include <stdio.h>
void hello(void){
printf("Hello World");
}
int main(void){
hello();
return 0;
}
gRPC APIについて
クライアントとサーバ間のデータのやり取りは時代に応じて変化しており、従来のRPCやREST APIではHTTP/HTTPSベースでJSONデータのやり取りを行っています。REST APIについてはこちらの記事で同期が分かりやすくまとめてくれています。HTTP/HTTPSのデメリットとして、テキストベースで情報をやり取りするためデータの転送効率が悪い、バイナリデータを扱いにくいといった点が挙げられます。
gRPCはそれらのデメリットを解決しつつ、RPCの要件を満たすためにHTTP/2のPOSTリクエストとそのレスポンスを使ってこれを実現しています。呼び出す関数はリクエストのパスに含め、引数や戻り値はHTTPレスポンスボディに含めます。
gRPCの特徴
HTTP/2プロトコルの使用
HTTP/2は双方向のストリーミング通信をサポートしており、長時間の双方向、多重通信が可能です。もちろんREST APIのHTTP/1.1のようなリクエスト・レスポンス型のやり取りも可能です。
JSONの代わりにProtobufを使用
テキストベースであるJSONの代わりにProtobufというフォーマットを用いて転送効率の高速化やバイナリデータのやり取りを可能にしています。圧縮されたフォーマットであるため、JSONのように人間が読みやすい形にはなっていません。
多言語に対応
幅広い開発言語のサポートがあり、さまざまな言語で書かれたクライアントでも図のようにStubだけが必要で、異なる言語でも呼び出しが可能です。
Image Source : https://grpc.io/docs/what-is-grpc/introduction/
gRPCとREST APIの使い分け
REST
REST APIは、さまざまな形式で書かれたメッセージを受信・返信できますが、テキストベースの人間が読めるフォーマットで柔軟性があり、効率的で言語やプラットフォームにとらわれないJSONという形式が最も一般的に使用されます。gRPCより実装も簡単です。
外部ユーザーが理解しやすい公開APIであったり、シンプルなデータ通信を行いたい場合に使用します。
gRPC
リアルタイム通信が必要な場合、gRPCの双方向ストリーミング管理機能により、システムは単項のクライアント・レスポンス通信を待つことなく、リアルタイムでメッセージの送受信が可能です。
高速な通信やリアルタイムのストリーミングと大量のデータロードを必要とする内部システムに適しています。
デモ
goでquick startを動かしてみます。
動作環境
OS : WSL2 – Ubuntu 22.04.2 LTS
go:go1.18.1 linux/amd64
Protocol bufferコンパイラ:libprotoc 3.12.4
quick start
goをダウンロードします
$ rm -rf /usr/local/go && tar -C /usr/local -xzf go1.21.3.linux-amd64.tar.gz
$ export PATH=$PATH:/usr/local/go/bin
$ go version
バージョンが出てきたら完了
Protocol Bufferのコンパイラをダウンロードします
$ sudo apt install -y protobuf-compiler
$ protoc --version
3以上のバージョンなら完了です
プロトコルコンパイラ用のプラグインをインストールします
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
パスを通します。
$ export PATH="$PATH:$(go env GOPATH)/bin"
サンプルコードを入手します。
$ git clone -b v1.58.2 --depth 1 https://github.com/grpc/grpc-go
Quick startのサンプルディレクトリに移動します。
$ cd grpc-go/examples/helloworld
サーバを実行します。
$ go run greeter_server/main.go
2023/11/21 15:09:33 server listening at [::]:50051
別のターミナルを開いてクライアントを起動します。
$ go run greeter_client/main.go
2023/11/21 15:09:46 Greeting: Hello world
サーバ側でクライアント側の入力を確認します。
2023/11/21 15:09:46 Received: world
gRPCのサービスを追加してみます。
.protoファイルでサービスを定義しているので、
grpc-go/examples/helloworld/helloworld/helloworld.protoを編集します。
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
// Sends another greeting
rpc SayHelloAgain (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;
}
保存して.protoファイルを再コンパイルします。
$ protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
helloworld/helloworld.proto
新しいメソッドを追加する必要があるのでサーバを更新します。
grpc-go/examples/helloworld/greeter_server/main.goに次の関数を追加します。
func (s *server) SayHelloAgain(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello again " + in.GetName()}, nil
}
クライアント側で先ほど追加した関数を実行してみます。
grpc-go/examples/helloworld/greeter_client/main.goのmain()末尾に次のコードを追加します。
r, err = c.SayHelloAgain(ctx, &pb.HelloRequest{Name: *name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
忘れずに保存して実行します。
$ go run greeter_server/main.go
2023/11/21 15:19:09 server listening at [::]:50051
$ go run greeter_client/main.go --name=yamanaka
2023/11/21 15:19:48 Greeting: Hello yamanaka
2023/11/21 15:19:48 Greeting: Hello again yamanaka
サーバ側の応答
2023/11/21 15:19:48 Received: yamanaka
無事追加した関数を実行することができました。
まとめ
gRPCはRPCを実現するためにGoogleが開発したプロトコルでした。マイクロサービスなど、インタラクションが必要なシステム間のデータ伝送を高速化するために使われます。一概にREST APIとgRPCのどちらが優れているとは言えません。求められる要件によって適切なプロトコルは異なるので、よく考えて採用しましょう。
参考文献
gRPC Motivation and Design Principles | gRPC
サービス間通信のための新技術「gRPC」入門 | さくらのナレッジ