HTTPを使ったAPI定義には、REST APIとGraphQLがあります。これらのAPI定義は、外部に公開するための「外向け」のものです。それに対し、「内向け」のプロセス間通信(IPC)のAPI定義として、Googleによって発案されたgRPCがあります。
gRPCは、REST APIとGraphQLよりも高速であり、異なるプラットフォームと互換性があるため、プロセス間通信に向いています。
■ gRPCの概要
gRPCとは、名前の通り、リモートプロシージャコール(RPC)です。
RPCは昔からある技術であり、特徴として以下が挙げられます。
1)Linux、Windows、iOS、AndroidなどのOS、C/C++、C#、Java、JavaScript、PHP、Python、Objective-C、Kotlinなどのプログラミング言語に依存しないこと
2)インタフェース定義のスクリプトを使って、各OS/言語用のスタブコードを自動生成すること
これによって、異なる環境であっても、ローカル関数の呼び出しと同様にプロセス間通信を実現できます。
gRPCも上記と同様の特徴を持ち、次のような仕組みで実現されます。
通信プロトコルに「HTTP/2」、インタフェース定義にGoogle発案の「ProtocolBuffers」を使います。
HTTP/2はストリームと呼ばれる通信の同時並列化、ProtocolBuffersはシリアライズと呼ばれる通信するデータ量の削減によって転送速度の向上が図られています。
インタフェース定義からツールを使って、クライアントとサーバ間で通信をするためのスタブコードを生成します。クライアントアプリは、スタブコードをリンクし、インタフェースを呼び出します。
サーバアプリは、スタブコードをリンクし、インタフェースの機能を実装することでサービスを提供します。
■ ProtocolBuffersでのインタフェース定義
データ型には、文字列(string)、整数(int32/64、uint32/64)、浮動小数点(float/double)、ブーリアン(bool)、バイト(byte)などがあります。これらを組み合わせ、構造体(message型)を定義することもできます。
インタフェースは、これらのデータ型を処理する関数として定義します。
例えば、以下のような定義となります。
// ProtocolBuffersバージョン3の規定を使用する
syntax = "proto3";
// MyMessage型を定義
message MyMessage {
string name = 1;
uint32 age = 2;
string message = 3;
}
// Helloリクエスト型
message HelloRequest {
uint32 no = 1;
}
// Helloレスポンス型
message HelloResponse {
bool result = 1;
MyMessage mymessage = 2;
}
// Helloインタフェース
service Hello {
// myfunc関数
rpc myfunc (HelloRequest) returns (HelloResponse) {}
}
このインタフェース定義からスタブコードを生成します。
Python環境では、gRPCを使うため次のパッケージをインストールします。
pip install grpcio grpcio-tools
その後、インタフェース定義(hello.proto)からスタブコードを生成します。
(カレントディレクトリにあるhello.protoファイルから、カレントディレクトリにスタブコードを生成します)
python -m grpc_tools.protoc -I. –python_out=. –grpc_python_out=. hello.proto
その結果、以下のように、スタブコードが生成されます。
hello_pb2.py ※データをシリアライズするためのコード
hello_pb2_grpc.py ※インタフェースのコード
■ PythonによるgRPCの実装サンプル
実装サンプルを以下に示します。
・クライアントのサンプルソース
# -*- coding: utf-8 -*-
import pprint
# スタブのインポート
import grpc
import hello_pb2
import hello_pb2_grpc
# リクエストを作成する
req = hello_pb2.HelloRequest(no=1)
# サーバーに接続する
connection=grpc.insecure_channel("127.0.0.1:50000")
# 送信先の「stub」を作成する
stub = hello_pb2_grpc.HelloStub(connection)
# リクエストを送信する
response = stub.myfunc(req)
# 取得したレスポンスの表示
pprint.pprint(response)
・サーバのサンプルソース
# -*- coding: utf-8 -*-
# gRPCのサーバー実装ではThreadPoolを利用
from concurrent.futures import ThreadPoolExecutor
# スタブのインポート
import grpc
import hello_pb2
import hello_pb2_grpc
# サービス定義から生成されたクラスを継承して、定義したリモートプロシージャに対応するメソッドを実装する
class Hello(hello_pb2_grpc.HelloServicer):
def myfunc(self, request, context):
# requestからクライアントから送信された値を取得する
no = request.no
print('request no = '+str(no))
# MyMessageインスタンスを作成する
mymessage = hello_pb2.MyMessage()
mymessage.name = "watanabe"
mymessage.age = 99
mymessage.message = "hello world"
# HelloResponseインスタンスを返す
return hello_pb2.HelloResponse(result=True,mymessage=mymessage)
if __name__ == '__main__':
# Serverインスタンス作成
server = grpc.server(ThreadPoolExecutor(max_workers=2))
# Helloインスタンス、Serverインスタンスを登録
hello_pb2_grpc.add_HelloServicer_to_server(Hello(), server)
# 50000番ポートで待ち受け
server.add_insecure_port('[::]:50000')
# 開始
server.start()
# 後処理
server.wait_for_termination()
実行結果は以下となります。
・クライアント
・サーバ
■ まとめ
プロセス間通信には、パイプ、キュー、ソケット、メッセージなど様々な方式があり、同期・非同期通信が実現されてきました。これらは密結合であり、限定されたソフトウェアやプログラム間でのみでの通信を行うためのものでした。
gRPCでは、より疎結合かつ汎用性のある方式を採用することで、プロセス間通信をオープンに実施できるようになります。これにより、モノシリックアーキテクチャからマイクロサービスアーキテクチャへの移行を促す技術となります。
ソフトウェア開発・システム開発業務/セキュリティ関連業務/ネットワーク関連業務/最新技術に関する業務など、「学習力×発想力×達成力×熱意」で技術開発の実現をサポート。お気軽にお問合せ下さい