基本チュートリアル

PythonでのgRPCの基本的なチュートリアル入門。

基本チュートリアル

PythonでのgRPCの基本的なチュートリアル入門。

このチュートリアルでは、Pythonプログラマー向けのgRPCの基本的な使い方を紹介します。

この例を段階的に実行することで、以下のことを学びます。

  • .protoファイルでサービスを定義する。
  • プロトコルバッファコンパイラを使用して、サーバーとクライアントのコードを生成する。
  • Python gRPC APIを使用して、サービス用のシンプルなクライアントとサーバーを記述します。

このチュートリアルでは、gRPCの概要を読み、Protocol Buffersに慣れていることを前提とします。proto3言語ガイドPython生成コードガイドでさらに詳しく知ることができます。

gRPCを使用する理由

私たちの例は、クライアントがルート上の機能に関する情報を取得したり、ルートの要約を作成したり、サーバーや他のクライアントと交通情報などのルート情報を交換したりできる、シンプルなルートマッピングアプリケーションです。

gRPCを使用すると、1つの.protoファイルでサービスを一度定義し、gRPCがサポートする任意の言語でクライアントとサーバーを生成できます。これにより、大規模データセンター内のサーバーから個人のタブレットまで、さまざまな環境で実行できます。異なる言語や環境間の通信の複雑さはすべてgRPCによって処理されます。また、効率的なシリアライゼーション、シンプルなIDL、簡単なインターフェース更新など、プロトコルバッファを使用する利点もすべて得られます。

サンプルコードとセットアップ

このチュートリアルのサンプルコードは、grpc/grpc/examples/python/route_guideにあります。サンプルをダウンロードするには、次のコマンドを実行してgrpcリポジトリをクローンしてください。

git clone -b v1.74.0 --depth 1 --shallow-submodules https://github.com/grpc/grpc

次に、リポジトリ内のexamples/python/route_guideに現在のディレクトリを変更します。

cd grpc/examples/python/route_guide

また、サーバーとクライアントのインターフェースコードを生成するために必要なツールがインストールされている必要があります。まだインストールしていない場合は、クイックスタート のセットアップ手順に従ってください。

サービスを定義する

最初のステップは(gRPCの概要で知っているように)、Protocol Buffersを使用してgRPCのサービスリクエストおよびレスポンスのメソッドタイプを定義することです。完全な.protoファイルは、examples/protos/route_guide.protoで確認できます。

サービスを定義するには、.protoファイルに名前付きのserviceを指定します。

service RouteGuide {
   // (Method definitions not shown)
}

次に、サービス定義内にrpcメソッドを定義し、リクエストとレスポンスの型を指定します。gRPCでは、4種類のサービスメソッドを定義できます。これらはすべてRouteGuideサービスで使用されています。

  • クライアントがスタブを使用してサーバーにリクエストを送信し、通常の関数呼び出しのようにレスポンスが返ってくるのを待つ*シンプルなRPC*。

    // Obtains the feature at a given position.
    rpc GetFeature(Point) returns (Feature) {}
    
  • レスポンスストリーミングRPC。クライアントはサーバーにリクエストを送信し、メッセージのシーケンスを読み取るためのストリームを取得します。クライアントは、メッセージがなくなるまで返されたストリームから読み取ります。例でわかるように、レスポンスタイプの前にstreamキーワードを配置することで、レスポンスストリーミングメソッドを指定します。

    // Obtains the Features available within the given Rectangle.  Results are
    // streamed rather than returned at once (e.g. in a response message with a
    // repeated field), as the rectangle may cover a large area and contain a
    // huge number of features.
    rpc ListFeatures(Rectangle) returns (stream Feature) {}
    
  • リクエストストリーミングRPC。クライアントはメッセージのシーケンスを書き込み、提供されたストリームを使用してサーバーに送信します。クライアントがメッセージの書き込みを完了したら、サーバーがそれらをすべて読み取ってレスポンスを返すのを待ちます。リクエストタイプの前にstreamキーワードを配置することで、リクエストストリーミングメソッドを指定します。

    // Accepts a stream of Points on a route being traversed, returning a
    // RouteSummary when traversal is completed.
    rpc RecordRoute(stream Point) returns (RouteSummary) {}
    
  • 双方向ストリーミングRPC。両側が読み書き可能なストリームを使用してメッセージのシーケンスを送信します。2つのストリームは独立して動作するため、クライアントとサーバーは好きな順序で読み書きできます。たとえば、サーバーはレスポンスを書き込む前にすべてのクライアントメッセージを受信するのを待つことも、メッセージを1つ読み取ってから1つ書き込むことも、その他の読み書きの組み合わせも可能です。各ストリーム内のメッセージの順序は保持されます。リクエストとレスポンスの両方の前にstreamキーワードを配置することで、このタイプのメソッドを指定します。

    // Accepts a stream of RouteNotes sent while a route is being traversed,
    // while receiving other RouteNotes (e.g. from other users).
    rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
    

.protoファイルには、サービスメソッドで使用されるすべてのリクエストおよびレスポンスタイプに対応するProtocol Bufferメッセージタイプ定義も含まれています。たとえば、Pointメッセージタイプは次のようになります。

// Points are represented as latitude-longitude pairs in the E7 representation
// (degrees multiplied by 10**7 and rounded to the nearest integer).
// Latitudes should be in the range +/- 90 degrees and longitude should be in
// the range +/- 180 degrees (inclusive).
message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

クライアントとサーバーのコードを生成する

次に、.protoサービス定義からgRPCクライアントおよびサーバーインターフェースを生成する必要があります。

まず、grpcio-toolsパッケージをインストールします。

pip install grpcio-tools

次のコマンドを使用してPythonコードを生成します。

python -m grpc_tools.protoc -I../../protos --python_out=. --pyi_out=. --grpc_python_out=. ../../protos/route_guide.proto

注:サンプルディレクトリにはすでに生成されたコードのバージョンが提供されているため、このコマンドを実行すると新しいファイルが作成されるのではなく、適切なファイルが再生成されます。生成されたコードファイルはroute_guide_pb2.pyroute_guide_pb2_grpc.pyと呼ばれ、以下が含まれています。

  • route_guide.protoで定義されたメッセージのクラス
  • route_guide.protoで定義されたサービス向けのクラス
    • RouteGuideStub。クライアントがRouteGuide RPCを呼び出すために使用できます。
    • RouteGuideServicer。RouteGuideサービスのインプリメンテーションのインターフェースを定義します。
  • route_guide.protoで定義されたサービス向けの関数
    • add_RouteGuideServicer_to_servergrpc.ServerにRouteGuideServicerを追加します。

カスタムパッケージパスでgRPCインターフェースを生成する

カスタムパッケージパスでgRPCクライアントインターフェースを生成するには、-Iパラメータとgrpc_tools.protocコマンドを組み合わせて使用します。このアプローチにより、生成されたファイルにカスタムパッケージ名を指定できます。

カスタムパッケージパスでgRPCクライアントインターフェースを生成するコマンド例は次のとおりです。

python -m grpc_tools.protoc -Igrpc/example/custom/path=../../protos \
  --python_out=. --grpc_python_out=. \
  ../../protos/route_guide.proto

生成されたファイルは、./grpc/example/custom/path/ディレクトリに配置されます。

  • ./grpc/example/custom/path/route_guide_pb2.py
  • ./grpc/example/custom/path/route_guide_pb2_grpc.py

このセットアップにより、生成されたroute_guide_pb2_grpc.pyファイルは、以下に示すように、カスタムパッケージ構造を使用してプロトバッファ定義を正しくインポートします。

import grpc.example.custom.path.route_guide_pb2 as route_guide_pb2

このアプローチに従うことで、指定されたパッケージパスに関してファイルが正しく呼び出されることを保証できます。この方法により、gRPCクライアントインターフェースにカスタムパッケージ構造を維持できます。

サーバーを作成する

まず、RouteGuideサーバーの作成方法を見てみましょう。gRPCクライアントの作成のみに関心がある場合は、このセクションをスキップしてクライアントの作成に直接進んでください(ただし、興味深いかもしれません!)。

RouteGuideサーバーの作成と実行は、2つの作業項目に分けられます。

  • サービス定義から生成されたサービサーインターフェースを、サービスの実際の「処理」を実行する関数で実装します。
  • クライアントからのリクエストをリッスンし、レスポンスを送信するためのgRPCサーバーを実行します。

例のRouteGuideサーバーは、examples/python/route_guide/route_guide_server.pyにあります。

RouteGuideを実装する

route_guide_server.pyには、生成されたクラスroute_guide_pb2_grpc.RouteGuideServicerをサブクラス化するRouteGuideServicerクラスがあります。

# RouteGuideServicer provides an implementation of the methods of the RouteGuide service.
class RouteGuideServicer(route_guide_pb2_grpc.RouteGuideServicer):

RouteGuideServicerは、すべてのRouteGuideサービスメソッドを実装しています。

シンプルなRPC

まず最も単純なタイプであるGetFeatureを見てみましょう。これは、クライアントからPointを取得し、データベースから対応するフィーチャー情報をFeatureで返します。

def GetFeature(self, request, context):
    feature = get_feature(self.db, request)
    if feature is None:
        return route_guide_pb2.Feature(name="", location=request)
    else:
        return feature

メソッドには、RPC用のroute_guide_pb2.Pointリクエストと、タイムアウト制限などのRPC固有の情報を提供するgrpc.ServicerContextオブジェクトが渡されます。route_guide_pb2.Featureレスポンスを返します。

レスポンスストリーミングRPC

次に、次のメソッドを見てみましょう。ListFeaturesは、複数のFeatureをクライアントに送信するレスポンスストリーミングRPCです。

def ListFeatures(self, request, context):
    left = min(request.lo.longitude, request.hi.longitude)
    right = max(request.lo.longitude, request.hi.longitude)
    top = max(request.lo.latitude, request.hi.latitude)
    bottom = min(request.lo.latitude, request.hi.latitude)
    for feature in self.db:
        if (
            feature.location.longitude >= left
            and feature.location.longitude <= right
            and feature.location.latitude >= bottom
            and feature.location.latitude <= top
        ):
            yield feature

ここでは、リクエストメッセージは、クライアントがFeatureを検索したいroute_guide_pb2.Rectangleです。単一のレスポンスを返す代わりに、メソッドはゼロ個以上のレスポンスをyieldします。

リクエストストリーミングRPC

リクエストストリーミングメソッドRecordRouteは、リクエスト値のイテレータを使用し、単一のレスポンス値を返します。

def RecordRoute(self, request_iterator, context):
    point_count = 0
    feature_count = 0
    distance = 0.0
    prev_point = None

    start_time = time.time()
    for point in request_iterator:
        point_count += 1
        if get_feature(self.db, point):
            feature_count += 1
        if prev_point:
            distance += get_distance(prev_point, point)
        prev_point = point

    elapsed_time = time.time() - start_time
    return route_guide_pb2.RouteSummary(
        point_count=point_count,
        feature_count=feature_count,
        distance=int(distance),
        elapsed_time=int(elapsed_time),
    )
双方向ストリーミング RPC

最後に、双方向ストリーミングメソッドRouteChatを見てみましょう。

def RouteChat(self, request_iterator, context):
    prev_notes = []
    for new_note in request_iterator:
        for prev_note in prev_notes:
            if prev_note.location == new_note.location:
                yield prev_note
        prev_notes.append(new_note)

このメソッドのセマンティクスは、リクエストストリーミングメソッドとレスポンスストリーミングメソッドのセマンティクスを組み合わせたものです。リクエスト値のイテレータが渡され、それ自体がレスポンス値のイテレータです。

サーバーを起動する

すべてのRouteGuideメソッドを実装したら、次のステップはgRPCサーバーを起動して、クライアントが実際にサービスを使用できるようにすることです。

def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    route_guide_pb2_grpc.add_RouteGuideServicer_to_server(RouteGuideServicer(), server)
    server.add_insecure_port("[::]:50051")
    server.start()
    server.wait_for_termination()

サーバーのstart()メソッドはノンブロッキングです。リクエストを処理するために新しいスレッドがインスタンス化されます。server.start()を呼び出すスレッドは、その間他の作業を行わないことがよくあります。この場合、server.wait_for_termination()を呼び出すことで、サーバーが終了するまで呼び出しスレッドをクリーンにブロックできます。

クライアントを作成する

完全なサンプルクライアントコードは、examples/python/route_guide/route_guide_client.pyで確認できます。

スタブを作成する

サービスメソッドを呼び出すには、まず*スタブ*を作成する必要があります。

.protoから生成されたroute_guide_pb2_grpcモジュールのRouteGuideStubクラスをインスタンス化します。

channel = grpc.insecure_channel('localhost:50051')
stub = route_guide_pb2_grpc.RouteGuideStub(channel)

サービスメソッドを呼び出す

単一のレスポンスを返すRPCメソッド(「レスポンス-ユニラテラル」メソッド)の場合、gRPC Pythonは同期(ブロッキング)および非同期(ノンブロッキング)の制御フローセマンティクスをサポートします。レスポンスストリーミングRPCメソッドの場合、呼び出しはすぐにレスポンス値のイテレータを返します。そのイテレータのnext()メソッドへの呼び出しは、イテレータからyieldされるレスポンスが利用可能になるまでブロックします。

シンプルなRPC

単純なRPCGetFeatureへの同期呼び出しは、ローカルメソッドの呼び出しとほぼ同じくらい簡単です。RPC呼び出しはサーバーからの応答を待ち、レスポンスを返すか例外を発生させます。

feature = stub.GetFeature(point)

GetFeatureへの非同期呼び出しも同様ですが、スレッドプールでのローカルメソッドの非同期呼び出しと同様です。

feature_future = stub.GetFeature.future(point)
feature = feature_future.result()
レスポンスストリーミングRPC

レスポンスストリーミングListFeaturesの呼び出しは、シーケンス型を扱うのと似ています。

for feature in stub.ListFeatures(rectangle):
リクエストストリーミングRPC

リクエストストリーミングRecordRouteの呼び出しは、ローカルメソッドにイテレータを渡すのと似ています。上記の単一レスポンスを返す単純なRPCと同様に、同期または非同期で呼び出すことができます。

route_summary = stub.RecordRoute(point_iterator)
route_summary_future = stub.RecordRoute.future(point_iterator)
route_summary = route_summary_future.result()
双方向ストリーミング RPC

双方向ストリーミングRouteChatの呼び出しは(サービスサイドと同様に)リクエストストリーミングとレスポンスストリーミングのセマンティクスを組み合わせたものです。

for received_route_note in stub.RouteChat(sent_route_note_iterator):

試してみましょう!

サーバーを実行する

python route_guide_server.py

別のターミナルから、クライアントを実行します。

python route_guide_client.py