Pythonを利用してgRPCに入門
今回はgRPCへ入門してみました。Pythonのチュートリアルを通して、どのように利用するのか学習しましたので、共有させてもらいます。
gRPCとは?
まずはgRPCとは何かについて簡単に解説します。gRPCは以下の公式ページからご確認いただけます。
まず、RPCとはRemote Procedure Callの頭文字を撮ったものであり、ネットワーク越しに別サーバにある機能を呼び出すためのプロトコルとなります。その中でgRPCはGoogleによって開発されたフレームワークとなります。
gRPCでは、クライアントアプリケーションは別のマシン上のサーバーアプリケーションのメソッドをローカルオブジェクトであるかのように直接呼び出すことができます。これを利用することにより、分散アプリケーションやサービスの作成が容易になります。他の多くのRPCシステムと同様に、gRPCはサービスを定義するという概念に基づいており、リモートから呼び出せるメソッドをそのパラメータと戻り値の型とともに指定します。サーバー側では、サーバーがこのインターフェースを実装し、クライアントからの呼び出しを処理するためのgRPCサーバーを実行します。クライアント側では、クライアントはサーバーと同じメソッドを提供するスタブを持ちます。
実際に使ってみる
今回は、二つの整数を受け取り、その値について足し算と引き算をさせるサービスを定義します。
サービスの定義
gRPCを利用するには、.proto
フォーマットとしてサービスを定義するところから始まります。今回は以下のようなファイルを用意しました。
syntax = "proto3";
package calculator;
service Calculator{
rpc Add (CalculatorRequest) returns (CalculatorResponse);
rpc Sub (CalculatorRequest) returns (CalculatorResponse);
}
message CalculatorRequest {
int32 x = 1;
int32 y = 2;
}
message CalculatorResponse {
int32 result = 1;
}
まず、syntax = "proto3";
でシンタックスの定義をし、package calculator;
で、calculator
というパッケージとして構築することを指定します。
次に、今回実装したいサービスを定義します。まず、以下の二つを定義していわゆるリクエストとレスポンスのスキーマを定義します。=1
などの番号はフィールド番号です。gRPCではフィールド番号を利用することでスキーマを一意に確定するために利用されるため、この番号は適切に設定される必要があります。
message CalculatorRequest {
int32 x = 1;
int32 y = 1;
}
message CalculatorResponse {
int32 result = 1;
}
最後に、先ほど定義したスキーマを参照してCalculator
サービスを定義します。
service Calculator{
rpc Add (CalculatorRequest) returns (CalculatorResponse);
rpc Sub (CalculatorRequest) returns (CalculatorResponse);
}
Pythonで利用する準備
まず、uv
を利用して環境を構築します。uv
については以下も参照ください。
uv init -p 3.12
mkdir generated
uv pip install grpcio grpcio-tools
source .venv/bin/activate
python -m grpc_tools.protoc -I proto --python_out . --grpc_python_out=. proto/calculator.proto
これを実行すると、gRPCに必要なPythonファイルが生成されます。calculator_pb2.py
とcalculator_pb2_grpc.py
の二つのファイルが作成されますが、手動で変更する必要は特になく、参照することで利用します。
サーバの実装
今回はローカルPC上で実装しますが、リモートPC上に置かれるであろうサーバコードを実装します。
import grpc
import time
import calculator_pb2
import calculator_pb2_grpc
from concurrent import futures
class CalculatorServicer(calculator_pb2_grpc.CalculatorServicer):
def __init__(self):
pass
def Add(self, request, context):
x = request.x
y = request.y
# protoファイルのResponseと一致させる
return calculator_pb2.CalculatorResponse(result=x+y)
def Sub(self, request, context):
x = request.x
y = request.y
# protoファイルのResponseと一致させる
return calculator_pb2.CalculatorResponse(result=x-y)
# start server
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
calculator_pb2_grpc.add_CalculatorServicer_to_server(CalculatorServicer(), server)
server.add_insecure_port('[::]:5051')
server.start()
print('run server')
# wait
try:
while True:
time.sleep(3600)
except KeyboardInterrupt:
# stop server
server.stop(0)
CalculatorServier
というクラス名で、呼び出される関数の中身を定義しています。calculator.proto
で定義した内容に従って、Add
とSub
二つの関数を定義しています。それ以外の部分については、grpc.server
でサーバを定義したのちにサーバを起動してクライアントからの要求を待機します。
クライアントの実装
次にクライアントを実装します。
import grpc
import calculator_pb2
import calculator_pb2_grpc
with grpc.insecure_channel('localhost:5051') as channel:
stub = calculator_pb2_grpc.CalculatorStub(channel)
x, y = 1, 2
add_result = stub.Add(calculator_pb2.CalculatorRequest(x=x, y=y))
sub_result = stub.Sub(calculator_pb2.CalculatorRequest(x=x, y=y))
print(f"{x} + {y} = {add_result.result}")
print(f"{x} - {y} = {sub_result.result}")
見ての通り、クライアント側ではスタブを取得することでgRPC呼び出しのための情報を取得し、Add
、Sub
に対して引数を指定して呼び出しています。その結果はCalculatorResponse
で定義した形式で取得できるため、result.response
の形式で結果を取得できます。
実行してみる
実行の仕方ですが、server.py
を先に起動した状態でclient.py
を実行すると以下の結果が得られます。
1 + 2 = 3
1 - 2 = -1
このように、足し算と引き算が実行されていることが確認されました。
リモートPCにある機能を呼ぶ場合
今回の検証はローカルPCで環境を作っていますが、実際にはリモートPC上にサーバが置かれることが多いでしょう。その場合、client.py
においてwith grpc.insecure_channel("localhost:5051")
のlocalhost
部分をリモートサーバのIPアドレスに置き換えるといいでしょう。
まとめ
今回はgRPCに入門してみました。今まで使う機会がなかったので触ってなかったですが、今回使ってみて簡単に構築できることがわかりました。今後はもっと複雑な機能についても実装してみたいと思います。ぜひみなさんもgRPCに入門してみてください!
Discussion