はじめに
第1章 gRPCとRESTの違い
第2章 .protoファイルを書いてみよう
第3章 Go言語でつくるgRPCサーバー
第4章 RailsアプリケーションからgRPCサーバーにアクセスする
第5章 インターセプタでログや認証を追加してみよう
第6章 単方向ストリーミングでつくる画像アップロードAPI
第7章 双方向ストリーミングでつくるリアルタイムリバーシ
付録A Google APIに学ぶprotoスタイルガイド
付録B Protocol Buffersの自動コード生成仕様(Go編)
付録C Protocol Buffersの自動コード生成仕様(Ruby編)
あとがき
gRPCはGoogleが開発した高速なAPI通信とスキーマ駆動開発を実現するRPCフレームワークであり、マイクロサービス間の内部通信を実現する有力な選択肢として活用されはじめています。
本書ではサーバー側(Go)/ クライアント側(Ruby)と異なる言語を用いて、いくつかのサンプルアプリケーションを実装しながら、gRPCとRESTの違い、Protocol Buffersにおけるスキーマの文法、単純なRPCから双方向ストリーミングRPCまでのgRPCにおける基本的な実装方法などを平易に説明します。
本書のプログラムは次の環境で作成・確認しています
・OS:macOS 10.14.6
・Go:1.13.4
・Ruby:2.6.2
macOS以外でも動作することが期待されますが、動作確認などはしておりません。また環境構築を行う箇所でも、macOS以外での方法に言及する場合もありますが、基本的にはmacOSを中心に解説しています。あらかじめご了承ください。
本書で使用したサンプルコードはGitHub上で公開されています。ぜひお手元で実際に動かして確認してみてください。
・2-4章 https://github.com/gami/grpc_example
・5章 https://github.com/gami/grpc_example/tree/chapter_5
・6章 https://github.com/gami/grpc_image_upload_example
・7章 https://github.com/gami/grpc_realtime_reversi
誤植や不正確な表現などありましたら、著者のTwitterアカウントでサポートしますので、お問い合わせください。読んでいて不明な点などあった場合なども、遠慮なく質問いただければと思います。
・https://twitter.com/gami
本書に掲載された内容は、情報の提供のみを目的としています。したがって、本書を用いた開発、制作、運用は、必ずご自身の責任と判断によっておこなってください。掲載内容の瑕疵により何か悲しい出来事が起こった場合でも著者は責任を負うことができません。
gRPCはRPCフレームワークのひとつです。RPCはRemote Procedure Callの略で、逐語的に訳すと「遠隔手続き呼び出し」となります。すなわち、「あるプログラムがネットワーク上の異なる場所に配置されたプログラムを呼び出して実行すること」を指します。
2004年ごろから、GoogleはStubbyという名前のRPCフレームワークを、自社データセンター内で活用してきました。StubbyはGoogleの各マイクロサービス間において、秒間100億リクエスト以上の処理をさばいていました1が、HTTP2などの標準規格の登場により、汎用的な規格にあわせてつくり直すことで、モバイルやIoT、クラウドなどのユースケースに対応していくべき2と判断されるようになりました。そのような流れの中で、2015年2月3にオープンソースのプロダクトとして公開されたものがgRPCです。
gRPCとしての歴史は5年を超えたくらいなので、まだ歴史が浅いように見えるかもしれません。しかしStubby時代も含めると、15年以上に渡ってGoogleという世界最大の分散システムを支えてきています4。この点から考えると、gRPC自体の運用実績はすでに十分にある5といえるでしょう。
Remote Procedure Callを訳すと遠隔手続き呼び出しという意味になります。すなわち、あるサービスから別のサービスのアプリケーションの処理(サブルーチン/クラス/関数など)を呼び出す技術です。RPCを使うことで、違うアプリケーションのロジックを、あたかも自分のアプリケーションの中に実装されているかのように扱うことができます。RPCは1976年にサン・マイクロシステムズがNFS(分散ファイルシステム)の一部で使うために開発した、とても歴史のある技術です。RPCでよく使われている技術には、gRPC以外にもJSON-RPC6やSOAP7、Apache Thrift8などがあります。
WebAPIによく使われるRESTも、離れたサーバーの機能を呼び出すという点で、RPCと同じ役割で使われるように思えます。ただし、RESTには「リソース志向を強く打ち出している」という思想的な違いがあります。リソース志向とはリソース(オブジェクト)を中心に考え、これに対してHTTPメソッドで操作していく考え方です。RPCではメソッドの呼び出しが基点となり、データはあくまでその副産物であるので、考え方としては逆になります。この志向性の違いから、RESTとRPCは対比的に捉えられています。
RESTでは、一般的にはJSONがよく使われます。この点はRESTとRPCの比較になりうるでしょうか?
RPCにも前に述べたようにJSON-RPCがあるので、レスポンスにJSONを使うかどうかはRESTとRPCの違いにはなりません9。つまり少なくともフォーマットという点では、RPCとそれ以外のものを区別する特徴にはならないと考えられます。
RESTは規格が厳格に決められたものではなく、シンプルでスケーラブルなAPIをつくるための「設計原則10」です。そのため、RESTでは原則に沿って自分で仕様を決めて実装することが求められます。一方RPCフレームワークは、規格や仕様に沿って実装されたライブラリーやフレームワークとして提供されます。RESTはかなり普及した技術なので、原則にしたがってきちんと設計されたREST APIは、多くの開発者の参加を容易にします。RPCフレームワークの場合は、そのフレームワークを学習するというところが、まず課題になるかもしれません。
次節以降では、RESTと比較してgRPCにどのような長所や短所があるのかという点から、gRPCとRESTの違いについて具体的に解説していきたいと思います。
gRPCの長所から見ていきます。gRPCの優れた点として大きく3つの点が挙げられます。
・HTTP/2による高速な通信
・Protocol Buffers
・柔軟なストリーミング形式
順に解説していきます。
gRPCではHTTP/2のプロトコル上で通信が行われます。そもそも、HTTP/2は既存のHTTP/1.1と比較して、何が違うのでしょうか。
まず、HTTP/2では通信時にデータがテキストではなくバイナリにシリアライズされて送られます。そのため、小さな容量で転送でき、ネットワーク内のリソースをより効率的に使用することができます。また、HTTP/2ではひとつのコネクションで複数のリクエスト/レスポンスをやり取りできます。そのためgRPCでも、コネクションは常時張られっぱなしの状態になります。リクエストの度に接続と切断をおこなう必要がなく、またヘッダーを都度送る必要がないので、より効率的な通信になります。
この高速であるという点において、gRPCはマイクロサービスの内部APIの通信規格として、評価されています。マイクロサービスでは、複数のサービスがそれぞれのAPIを通じて緊密な連携を行う必要があるため、プライベートなAPIへのリクエストが増える傾向にあり、それぞれの応答速度の遅延が、サービス全体のボトルネックになりがちだからです。
gRPCではProtocol Buffersのフォーマットにシリアライズしてデータをやり取りします。シリアライザ自体はカスタマイズすることでJSONなどのレスポンスに変えることもできます11。
Protocol BuffersもgRPCと同様にGoogleが開発しました。こちらはgRPCよりも少し早く2008年にオープンソース化されました。バイナリ形式なので、そのような意味ではMessagePack12に似ているといえるかもしれません。
Protocol Buffersの一番の特徴は.protoファイルというIDL(インタフェース記述言語)です。.protoファイルを書いて、コンパイラを実行すると任意の言語のサーバー/クライアント用コードを自動生成してくれます。自分でAPIインターフェースを実装したり、シリアライズされたデータのエンコード/デコード処理を書く必要はありません。
このためgRPCで開発する場合は、まずスキーマを書くことになります。API側とクライアント側でそれぞれ別の開発者が担当しているような状況下で、API仕様書が無かったり、もしくは仮に仕様書が存在しても古いまま更新が遅れてしまったことで、開発に支障が生じてしまったという経験は珍しくないと思います。
gRPCではスキーマが最初に書かれるので、.protoファイルを見ればAPIの仕様は常に明確です。そのため、必然的にスキーマファーストの開発を行うことになります13。
また.protoファイルでは静的型付けを行います。IDLで記述された仕様は、各言語のコード生成時に適切な型へ変換されます。
言語はC++、Java、Python、Go、Ruby、PHP、C#、Node.js、Andorid Java、Objective-C、Dart、ブラウザ上でのJavaScript(gRPC Web)に公式対応しています。
Protocol BuffersはgRPCの最大のメリットと考えてよいかと思います。
gRPCではAPIで一般的な、ひとつのリクエストに対してひとつのレスポンスを送る形式以外に、単方向/双方向のストリーミングRPCに対応しています。この節ではgRPCが対応している4つのRPC方式について説明します。
クライアントから送られたひとつのリクエストに対して、レスポンスを一度返して終了する、もっとも一般的な形式です。同期的に処理できるので、サーバーやクライアントの実装もシンプルなものになります。14
クライアントから送られたリクエストに対して、レスポンスを複数回に分けて返します。時間のかかる処理について、非同期的にレスポンスを返すことができます。
クライアントからのリクエスト後、クライアントは待機状態とし、サーバーの状態変更に応じて、情報を随時プッシュしていくこともできます。この形式はお知らせ通知やタイムラインのリアルタイム更新などに有効です。
サーバー側の状態変化をクライアントで検知したい場合にストリーミングが無いと、定期的な通信(ポーリング)をクライアントから行なう必要があり、無駄な通信が多く発生してしまいます。サーバーストリーミングRPCを使えば、サーバー側から状態変化時にクライアントに直接伝えることができるので、ネットワーク負荷を最小化することができます。
クライアントからリクエストを分割して送り、サーバーは全てのリクエストを受け取る前に処理を逐次開始できます。サーバー側は全てのリクエストを受け取ってからレスポンスを返します。
大きなデータを分割してアップロードしたい場合などに有用です。
クライアントから初めのリクエストが送られた後、サーバー・クライアントどちらも任意のタイミングでリクエスト・レスポンスを送ることができます。まず思いつくユースケースとしてはチャットやゲームなどでしょうか。
REST APIではストリーミングが行えないので、WebSocketサーバーを別途立てる必要などがありました。gRPCの場合、単一のサーバーで双方向通信が必要なAPIとしても対応可能です。
これまでgRPCの優れた部分について記述してきました。しかし、現状ではgRPCを導入する際に支障になるであろうことも少なからずあります。
現時点(2020年3月)において、AWSのALB(Application Load Balancer)はgRPCに対応していません15。
HTTP/2のリクエストを受け付けることはできますが、バックエンドのサーバーへの転送をHTTP/1で行うためです。同様に通信経路上でHTTP/2に非対応なサービスがある場合に、gRPCを使う上で問題が生じる場合があります。
NginxがgRPCをサポートする16など少しずつ対応環境は増えつつありますが、枯れたHTTP/1.1の上で行われるRESTの通信と比べて、HTTP/2に関連した地雷を踏みやすいことは否めません。
2018年11月にgRPC-Webが正式リリースになりました17。ただしEnvoyなどを使ってProxyサーバーを立てる必要があったり、双方向あるいはクライアントストリーミングRPCに非対応であるなど、まだまだ十分な対応状況とはいえないようです18。フロントエンドのアプリケーションとの通信に使うにはまだ少し早いかもしれません。
REST APIの方が、JSONとの親和性の高さもありSPA19との相性はよいといえます。
前に.protoファイルからgRPCクライアント/サーバー用のボイラープレートコード生成に、多くの言語で公式対応していると述べました。ただし生成に対応しているとはいっても、各言語でのgRPCの実装状況はまちまちであり、使える機能には差があります。
たとえば、GoとJavaにはクライアントロードバランシングの機能が実装されていますが、Rubyにはありません20。インターセプタなどの機能もほとんどの言語で対応しているものの、RubyやPHPなどではExperimentalとされており、実運用時には注意が必要です。GoやJavaといった言語と比べて他の言語(特にgRPC Coreの実装を待って機能が追加される言語)は機能追加が遅くなる傾向にあります。
JSONにシリアライズされたフォーマットを返すAPIであれば、ローカル開発中にcURLやブラウザなどで叩いて、簡単に確認することができます。REST API開発中は出力を目視で確認することも多いと思います。
gRPCの場合は専用のクライアントをインストールする必要があります21。
ベンチマーク22によると、gRPCはREST(JSON)に比べて確かに速いのですが、何倍も改善できるほど劇的に速いわけではありません。このベンチマークでは並列リクエストかつ高負荷な状態でも40%の高速化であり、低負荷なテストの場合はRESTの方が速い結果になることもありました。
Webアプリケーションのパフォーマンスチューニングでは考慮すべき点は多数あり、慣れないフレームワークの導入に工数をかけるよりも、たとえばデータベースやキャッシュ戦略などに注力する方が大きな改善を得られる可能性はあります。
gRPCの長所と短所を振り返ってみましょう。
これまで述べてきたように、gRPCには次の利点があります。*HTTP/2による軽量かつ高速な通信*Protocol Buffersによる型安全なコード自動生成*柔軟なストリーミング形式に対応
一方でRESTに慣れている開発者も多く、環境によってはRESTを選択するケースの方が有効な場合も、現状ではまだあるといえます。
その上で筆者がgRPCを推すもっとも大きなポイントはProtocol Buffersによるスキーマファースト開発の生産性です。
速度が求められるような環境や、ストリーミングが求められるユースケースは常にあるわけではありませんが、スキーマを最初に書き、ボイラープレートコードを自動生成してから開発を始めるという流れは、どのようなチームでも非常に有益です。
ケースバイケースで考えてRESTを選択する場合もまだありますが、うまく適用できそうな要件には、ぜひ積極的に検討してみていただきたいと思います。
次章以降では、.protoファイルの書き方について解説していきます。
gRPCのRPCはRemote Procedure Callです。
ではgは何でしょう?
Googleのgでしょうか?
実はリリースバージョンによって、先頭のgの意味は違います。
v1.0ではgはgRPCの略です。そして、1.19ではgoldの略です。この原稿執筆時点で、1.28まで決められています。
gの意味について、表1.1にまとめてみました。それぞれのgの由来は公開されていませんが、単語の意味や固有名詞、gRPC開発者リストなどからそれらしいものを調べています。
バージョン | 意味 | 日本語訳 |
1.0 | gRPC | gRPC |
1.1 | good | よき |
1.2 | green | 緑の。gRPCのイメージカラーはエメラルドグリーン。 |
1.3 | gentle | やさしい |
1.4 | gregarious | 草などがわらわら生えてる様子 |
1.6 | garcia | gRPC開発者のDavid Garcia Quintasと思われる。 |
1.7 | gambit | (チェスなどの)初手 |
1.8 | generous | 思いやりがある |
1.9 | glossy | つやつやしている |
1.10 | glamorous | 豊満で魅力的な |
1.11 | gorgeous | 豪華な。なおTwitterアンケートでgoawayに決まったが無視された。 |
1.12 | glorious | 栄光ある。 |
1.13 | gloriosa | 熱帯に生える花の名前で球根に毒がある。 |
1.14 | gladiolus | 花の名前(グラジオラス)で根は湿布の材料になる。 |
1.15 | glider | (滑空する)グライダー。速そうである。 |
1.16 | gao | gRPC開発者のYang Gaoと思われる |
1.17 | gizmo | 映画グレムリンにでる始めだけ可愛い生き物。 |
1.18 | goose | ガチョウ |
1.19 | gold | 金(たまにガチョウから出てくる) |
1.20 | godric | ハリーポッターに出てくる古の魔法使いゴドリック・グリフィンドール |
1.21 | gandalf | 指輪物語に出てくる魔法使い |
1.22 | gale | 疾風 |
1.23 | gangnam | 韓国ソウル特別市江南区。江南スタイルで有名。 |
1.24 | ganges | ガンジス川 |
1.25 | game | ゲーム |
1.26 | gon | 角度の単位か、あるいはHunter × Hunterの主人公 |
1.27 | guantao | gRPC開発者のGuantao Liuと思われる |
1.28 | galactic | 銀河のように大きい |
gRPCではシリアライズフォーマットとしてProtocol Buffersを使います。Protocol Buffersでは.protoを拡張子としてもつファイル上にスキーマ定義を行い、protocコマンドで各言語用のコードを生成します。したがって、gPRCでもこの.protoファイル上に、Protocol Buffersの仕様に基づいてスキーマ定義をおこなう必要があります。
この章では、gRPCのAPI定義ファイルとなる.protoファイルの書き方を解説します。
Potocol Buffersの最新バージョンは本書執筆時点(2020年3月)で3.11.4です。2016年に登場したv3で破壊的な変更が行われており、以降のバージョンについてproto3と呼ばれています。安定性の面からも、それ以前のproto2ではなくproto3を使うことが推奨されます。
説明用のサンプルとして、簡単な.protoファイルをひとつ用意してみました。電話帳検索のAPIを想定しています。
syntax = "proto3";
package myapp;
service AddressBookService {
rpc Search (SearchRequest) returns (SearchResponse);
}
message SearchRequest {
string name = 1;
}
message SearchResponse {
Person person = 1;
}
message Person {
int32 id = 1;
string name = 2;
string email = 3;
repeated PhoneNumber phone_numbers = 4;
enum PhoneType {
UNKNOWN = 0;
MOBILE = 1;
HOME = 2;
WORK = 3;
}
message PhoneNumber {
string number = 1;
PhoneType phone_type = 2;
}
}
このサンプルを使って、順に文法を解説していきます。