基本チュートリアル

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

基本チュートリアル

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

このチュートリアルでは、gRPCを扱うための基本的なPythonプログラマー向けの入門を提供します。

この例を試すことで、次の方法を学びます。

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

gRPC入門を読み、プロトコルバッファに精通していることを前提としています。詳細については、proto3言語ガイドおよびPython生成コードガイドを参照してください。

なぜgRPCを使うのか?

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

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

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

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

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

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

$ cd grpc/examples/python/route_guide

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

サービスの定義

最初のステップ(gRPC入門からわかるように)は、プロトコルバッファを使用して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つのストリームは独立して動作するため、クライアントとサーバーは好きな順序で読み取りと書き込みを行うことができます。たとえば、サーバーはすべてのクライアントメッセージを受信するのを待ってからレスポンスを書き込むことも、メッセージを読み取ってからメッセージを書き込むことを交互に行うことも、その他読み取りと書き込みの組み合わせを行うこともできます。各ストリームのメッセージの順序は保持されます。リクエストとレスポンスの両方の前で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ファイルには、サービスメソッドで使用されるすべてのリクエストおよびレスポンスタイプに関するプロトコルバッファメッセージタイプ定義も含まれています。たとえば、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で定義されたサービスのクラス
    • クライアントがRouteGuide RPCを呼び出すために使用できるRouteGuideStub
    • RouteGuideサービスの実装のインターフェースを定義するRouteGuideServicer
  • route_guide.protoで定義されたサービスの関数
    • grpc.ServerにRouteGuideServicerを追加するadd_RouteGuideServicer_to_server

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

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

カスタムパッケージパスで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です。メソッドは単一のレスポンスを返すのではなく、0個以上のレスポンスを生成します。

リクエストストリーミング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()メソッドの呼び出しは、イテレータから生成されるレスポンスが利用可能になるまでブロックします。

シンプルなRPC

単純なRPC GetFeatureへの同期呼び出しは、ローカルメソッドを呼び出すのとほぼ同じくらい簡単です。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