Web●●●○○

gRPC

じーあーるぴーしー

スキーマ定義から各言語のコードを生成し、HTTP/2上で高速に呼び合うRPCフレームワーク。

概要

gRPC は、Google が開発したオープンソースの RPC(Remote Procedure Call — 別マシンの関数を、あたかも手元の関数のように呼び出す仕組み)フレームワークです。サービス間のやり取りをまず .proto というスキーマファイルに「どんな関数が、どんな型の引数を受け取り、どんな型の値を返すか」として定義し、そこから各プログラミング言語のクライアント・サーバコードを自動生成します。通信路には HTTP/2 を使い、データは Protocol Buffers というコンパクトなバイナリ形式で運びます。

登場する典型的な場面は、マイクロサービス同士の内部通信です。数十のサービスが1リクエストの裏で何度も呼び合うような環境では、通信の速さと型の厳密さがそのままシステムの品質になります。Kubernetes まわりのエコシステムをはじめ、クラウドネイティブなシステムの「内側の配線」として広く使われている技術です。

なぜ生まれたか

gRPC 以前、サービス間通信の主流は REST API + JSON でした。人間が読める形式で汎用性は高いのですが、内部通信の道具としては不満が残ります。まず JSON はテキスト形式のため、数値もすべて文字列として運ばれ、パース(解析)のコストとデータサイズが大きい。次に、API の形が仕様書という「文書」でしか共有されず、フィールド名の綴りミスや型の食い違いは実行してみるまで分かりません。サービスが増えるほど「A チームが返す JSON の形が B チームの想定とずれていた」という事故が積み上がります。

Google は社内で長年、Stubby という独自 RPC 基盤と Protocol Buffers で、この問題を「スキーマを唯一の契約とし、コードは機械に生成させる」方針で解決してきました。その知見を HTTP/2 の標準化に合わせて再設計し、2015年にオープンソースとして公開したのが gRPC です。人間が読む仕様書ではなく、機械が検証できるスキーマを共有の中心に置く — これが gRPC の設計思想の核です。

詳細

スキーマ駆動 — .proto からコードを生成する

開発はまず .proto ファイルを書くところから始まります。ここにメッセージ(データ構造)とサービス(呼び出せる関数の一覧)を定義し、protoc というコンパイラに通すと、Go・Java・Python・TypeScript など各言語向けのコードが生成されます。サーバ側にはインターフェース(中身を実装すべき骨組み)が、クライアント側にはスタブ(ネットワーク越しの呼び出しを普通の関数呼び出しに見せかける代理オブジェクト)が手に入ります。

.proto ファイルメッセージ定義とサービス定義protocコンパイラGo サーバ骨組み実装を埋めるだけJava クライアント型付きスタブPython クライアント型付きスタブ
Protocol Buffers のコード生成フロー — スキーマが言語をまたぐ唯一の契約になる

この仕組みの効能は「型の食い違いがコンパイル時に検出される」ことです。フィールド名の綴りミスも型の不一致も、実行前にビルドが失敗して教えてくれます。さらに各フィールドには番号が振られており、名前ではなく番号でエンコードするため、バイナリは小さく、フィールドの追加もスキーマの後方互換を保ったまま行えます。多言語が混在するチームでも、.proto さえ共有すれば全員が同じ契約のもとで開発できます。

HTTP/2 が可能にする4つの通信方式

gRPC が HTTP/2 を土台に選んだのは、単に速いからだけではありません。HTTP/2 の「1本の接続の上に複数のストリームを多重化できる」性質を使い、gRPC は4種類の通信方式を提供します。

1つめは単項 RPC(Unary)で、1リクエストに1レスポンスを返す、通常の関数呼び出しと同じ形です。2つめはサーバストリーミングで、1つのリクエストに対してサーバが結果を小分けに送り続けます(大量の検索結果や進捗通知など)。3つめはクライアントストリーミングで、クライアントがデータを送り続けて最後にサーバが1つの応答を返します(センサーデータやファイルのアップロードなど)。4つめは双方向ストリーミングで、両者が独立に送り合えます(チャットやリアルタイム同期など)。REST では WebSocket などを別途持ち出す必要があった用途が、同じフレームワーク・同じ型定義のまま書けるのが強みです。

このほか実務では、応答期限をクライアントが指定するデッドライン、呼び出しの連鎖ごと打ち切るキャンセル、認証情報などを運ぶメタデータ、といった分散システム向けの機能が最初から組み込まれている点も重宝されます。

REST との使い分けとブラウザの制約

gRPC と REST は競合というより住み分けです。よくある構成は「内部のサービス間通信は gRPC、外部に公開する API は REST」というものです。内部では性能と型安全の恩恵が大きい一方、外部公開では「curl ひとつで試せる」「JSON をどの言語でも扱える」という REST の取っつきやすさが勝ります。

大きな制約として、ブラウザから gRPC を直接話すことはできません。gRPC は HTTP/2 のフレームを細かく制御しますが、ブラウザの JavaScript にはそのレベルの API が開放されていないためです。対策は2つあり、1つはブラウザ向けの変換プロトコルである grpc-web を使い、中間のプロキシで本来の gRPC に変換する方法。もう1つは grpc-gateway のようなツールで .proto から REST API を自動生成し、外部には REST として公開する方法です。また、バイナリ形式は人間がそのまま読めないため、デバッグには grpcurl などの専用ツールが必要になることも、導入時に押さえておきたい運用上のコストです。

さらに、gRPC は接続を長く張りっぱなしにするため、接続単位で振り分ける L4 のロードバランサでは負荷が偏りやすい、という古典的な落とし穴もあります。リクエスト単位で分散させるには L7 のプロキシやクライアントサイドの負荷分散が必要で、これはサービスメッシュ導入の動機のひとつにもなっています。