はじめに
本記事では、Googleが開発した高速・汎用的なRPCフレームワークである gRPC について、以下の内容を整理して解説します。
- gRPCとは(概要、特徴)
- gRPCが実際に利用されている場面
- 類義技術との違い(GraphQL, REST)
- Pythonサンプルコード
- いつgRPCを使うか
1. gRPCとは
概要
- Remote Procedure Call (RPC) の仕組みを採用し、リモートの関数/メソッドを呼び出す。
- Google製のオープンソースで、HTTP/2とProtocol Buffersをベースに設計。
特徴
- 高速・軽量: バイナリシリアライズ(Protocol Buffers)を使い、JSONより効率的。
- HTTP/2: 多重化・ヘッダ圧縮・双方向ストリーミングをサポート。
- 多言語対応: C++, Java, Python, Go, Node.jsなど多くの言語で公式実装。
-
自動コード生成:
.proto
ファイルからクライアント/サーバーのスタブを生成。 - 双方向ストリーミング: クライアント⇄サーバー間で同時にデータ送受信可能。
2. gRPCが実際に利用されている場面
- マイクロサービス通信: 内部サービス間の高頻度な呼び出し(Netflix, Dropboxなど)
- 分散システム: データベースノード間通信(CockroachDB, etcd)
- 機械学習モデル配信: TensorFlow Serving のAPI
- サービスメッシュ: Istio / Envoy によるgRPCトラフィック管理
- モバイル/IoT: 通信量を抑えつつリアルタイム性を担保(Square(Block)など)
3. 類義技術との違い(GraphQL, REST)
比較項目 | gRPC | GraphQL | REST API |
---|---|---|---|
データ形式 | バイナリ(Protocol Buffers) | テキスト(JSON) | テキスト(JSON, XMLなど) |
通信プロトコル | HTTP/2 | HTTP/1.1 (通常) | HTTP/1.1 (一般的) |
ストリーミング | 双方向ストリーミング対応 | 標準では非対応 (Subscription等オプション) | 基本はリクエスト→レスポンス |
型安全性 | 高い(IDLで定義) | スキーマ定義あり | ランタイム検証が主 |
柔軟性 | 定義済RPCインターフェースに従う | クエリでフィールド選択可能 | エンドポイントごとに設計 |
主な用途 | サービス間通信/リアルタイム処理 | フロントエンド向け柔軟データ取得 | 公開API/ブラウザ連携 |
4. Pythonサンプルコード
以下の例では、シンプルな「Hello World」RPCに加え、クライアントサイド、サーバーサイド両方でのストリーミング例も紹介します。
4.1. ディレクトリ構成
grpc-python-example/
├── protos/
│ └── helloworld.proto
├── server/
│ ├── server.py
│ └── requirements.txt
└── client/
├── client.py
└── requirements.txt
4.2. 必要パッケージのインストール
各ディレクトリで以下を実行:
pip install grpcio grpcio-tools
4.3. Protocol Buffers 定義 (protos/helloworld.proto
)
syntax = "proto3";
package helloworld;
service Greeter {
// 単方向Unary RPC
rpc SayHello (HelloRequest) returns (HelloReply);
// サーバーストリーミングRPC
rpc SayHelloServerStream (HelloRequest) returns (stream HelloReply);
// クライアントストリーミングRPC
rpc SayHelloClientStream (stream HelloRequest) returns (HelloReply);
// 双方向ストリーミングRPC
rpc SayHelloBiDiStream (stream HelloRequest) returns (stream HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
.protoファイルは、gRPC(Google Remote Procedure Call)やProtocol Buffersなどのデータ交換フォーマットで使用される、スキーマ定義ファイルです。クライアントとサーバー間でデータの構造を共有し、データ交換の互換性を確保するために使われます。
コード生成コマンド
プロジェクトルートで実行:
python -m grpc_tools.protoc \
-I=protos \
--python_out=server \
--grpc_python_out=server \
--python_out=client \
--grpc_python_out=client \
protos/helloworld.proto
4.4. サーバー実装 (server/server.py
)
from concurrent import futures
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
import time
class GreeterServicer(helloworld_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
return helloworld_pb2.HelloReply(
message=f"[Unary] Hello, {request.name}!"
)
def SayHelloServerStream(self, request, context):
for i in range(5):
yield helloworld_pb2.HelloReply(
message=f"[ServerStream] Hello {request.name}, msg {i+1}"
)
time.sleep(0.5)
def SayHelloClientStream(self, request_iterator, context):
names = [req.name for req in request_iterator]
combined = ",".join(names)
return helloworld_pb2.HelloReply(
message=f"[ClientStream] Hello {combined}!"
)
def SayHelloBiDiStream(self, request_iterator, context):
for req in request_iterator:
yield helloworld_pb2.HelloReply(
message=f"[BiDiStream] Hello {req.name}!"
)
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
helloworld_pb2_grpc.add_GreeterServicer_to_server(
GreeterServicer(), server
)
server.add_insecure_port('[::]:50051')
server.start()
print("gRPC server started on port 50051")
server.wait_for_termination()
if __name__ == '__main__':
serve()
4.5. クライアント実装 (client/client.py
)
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
def run():
channel = grpc.insecure_channel('localhost:50051')
stub = helloworld_pb2_grpc.GreeterStub(channel)
# 1) Unary RPC
resp1 = stub.SayHello(
helloworld_pb2.HelloRequest(name='name1')
)
print(resp1.message)
# 2) Server Streaming
for reply in stub.SayHelloServerStream(
helloworld_pb2.HelloRequest(name='StreamUser')
):
print(reply.message)
# 3) Client Streaming
names = ['Alice', 'Bob', 'Charlie']
requests = (helloworld_pb2.HelloRequest(name=n) for n in names)
resp3 = stub.SayHelloClientStream(requests)
print(resp3.message)
# 4) BiDi Streaming
def request_generator():
for n in ['X', 'Y', 'Z']:
yield helloworld_pb2.HelloRequest(name=n)
for reply in stub.SayHelloBiDiStream(request_generator()):
print(reply.message)
if __name__ == '__main__':
run()
5. いつgRPCを使うか
いつgRPCを使うか
以下のような要件がある場合、gRPCの採用を検討すると良いでしょう。
- 高パフォーマンスが必須:低レイテンシ・少ない帯域消費を実現したい
- 双方向ストリーミング:リアルタイムデータの送受信が必要
- マイクロサービス間通信:多言語スタックのサービス間で型安全に連携したい
- 厳密なIDL管理:API仕様を明確に定義して自動生成したい
- 既存のHTTP/2インフラがある:ロードバランサやサービスメッシュでHTTP/2を活かせる