基本チュートリアル
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.py
とroute_guide_pb2_grpc.py
と呼ばれ、以下が含まれます。
route_guide.proto
で定義されたメッセージのクラスroute_guide.proto
で定義されたサービスのクラス- クライアントがRouteGuide RPCを呼び出すために使用できる
RouteGuideStub
- RouteGuideサービスの実装のインターフェースを定義する
RouteGuideServicer
- クライアントがRouteGuide RPCを呼び出すために使用できる
route_guide.proto
で定義されたサービスの関数grpc.Server
にRouteGuideServicerを追加するadd_RouteGuideServicer_to_server
注意
pb2の2
は、生成されたコードがProtocol Buffers Python APIバージョン2に従っていることを示します。バージョン1は廃止されています。これは、.proto
ファイルのsyntax = "proto3"
またはsyntax = "proto2"
で示されるProtocol Buffers言語バージョンとは関係ありません。カスタムパッケージパスで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