😊

Pythonを利用してgRPCに入門

に公開

今回はgRPCへ入門してみました。Pythonのチュートリアルを通して、どのように利用するのか学習しましたので、共有させてもらいます。

gRPCとは?

まずはgRPCとは何かについて簡単に解説します。gRPCは以下の公式ページからご確認いただけます。

https://grpc.io/docs/what-is-grpc/

まず、RPCとはRemote Procedure Callの頭文字を撮ったものであり、ネットワーク越しに別サーバにある機能を呼び出すためのプロトコルとなります。その中でgRPCはGoogleによって開発されたフレームワークとなります。

gRPCでは、クライアントアプリケーションは別のマシン上のサーバーアプリケーションのメソッドをローカルオブジェクトであるかのように直接呼び出すことができます。これを利用することにより、分散アプリケーションやサービスの作成が容易になります。他の多くのRPCシステムと同様に、gRPCはサービスを定義するという概念に基づいており、リモートから呼び出せるメソッドをそのパラメータと戻り値の型とともに指定します。サーバー側では、サーバーがこのインターフェースを実装し、クライアントからの呼び出しを処理するためのgRPCサーバーを実行します。クライアント側では、クライアントはサーバーと同じメソッドを提供するスタブを持ちます。

実際に使ってみる

今回は、二つの整数を受け取り、その値について足し算と引き算をさせるサービスを定義します。

サービスの定義

gRPCを利用するには、.protoフォーマットとしてサービスを定義するところから始まります。今回は以下のようなファイルを用意しました。

proto/calculator.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については以下も参照ください。

https://zenn.dev/akasan/articles/39f81f8bd15790

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.pycalculator_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で定義した内容に従って、AddSub二つの関数を定義しています。それ以外の部分については、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呼び出しのための情報を取得し、AddSubに対して引数を指定して呼び出しています。その結果は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