0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

#0119(2025/05/04)gRPC入門

Posted at

はじめに

本記事では、Googleが開発した高速・汎用的なRPCフレームワークである gRPC について、以下の内容を整理して解説します。

  1. gRPCとは(概要、特徴)
  2. gRPCが実際に利用されている場面
  3. 類義技術との違い(GraphQL, REST)
  4. Pythonサンプルコード
  5. いつ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を活かせる
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?