🍣

ざっくり理解するgRPC入門

2024/08/07に公開

この記事の概要

こんにちは、エンジニアの加藤(@tomo_k09)です。

最近、はじめてgRPCを使う機会があったので、知識ゼロの状態でもざっくりとgRPCとは何かについてイメージがつくように、gRPCとはいったい何なのかやgRPCを使った実装例についてまとめてみました。

「gRPCって何かわからない」、「gRPCってなんとなく知っているけど、もう少し理解したい」という方には役に立つないようになっていると思います。
これからgRPCを学びたいという方の参考になれば幸いです。

gRPCとは

gRPCは、Googleが開発したオープンソースのリモートプロシージャコール(RPC)システムで、異なるサーバーやサービス間で効率的にデータをやり取りするための技術です。

gRPCの主な用途

gRPCの代表的な使い方としては、以下の3つが挙げられます。

1:マイクロサービス間の通信
サービス同士がデータや機能をやり取りする際に使用されます。例えば、支払いサービスがユーザー管理サービスにユーザー情報をリクエストする場合などです。

2:リアルタイム通信
gRPCは双方向ストリーミングをサポートしており、リアルタイムでデータの送受信が可能です。チャットアプリケーションやオンラインゲームなど、即時性が求められる場面で活用されます。

3:多言語対応のサービス同士の通信
gRPCは異なる言語で書かれたサービス間でシームレスに通信が可能です。例えば、バックエンドはJavaで、フロントエンドはNode.jsで開発する場合などに有効です。

後述しますが、PharmaXでは、RubyアプリケーションとPythonアプリケーション間の通信で、gRPCを使っています。

gRPCの特徴

gRPRの特徴としては以下のような点が挙げられます。

高速な通信が可能
データのシリアライズは非常に効率的で、データ伝送のオーバーヘッドが小さいため、高速な通信が可能です。

対応言語が豊富
gRPCは多くのプログラミング言語で使用でき、各言語でのクライアントやサーバーのコードを自動的に生成することができます。

<対応言語>

  • C# / .NET
  • C++
  • Dart
  • Go
  • Java
  • Kotlin
  • Node
  • Objective-C
  • PHP
  • Python
  • Ruby

参考:https://grpc.io/docs/languages/

様々な通信方式に対応

  • 単方向RPC(Unary RPC)
    • クライアントが単一のリクエストを送り、単一のレスポンスを受け取る最も簡単なタイプのRPCです
    • 用途としては、REST APIと非常に似ていてます(REST APIとの違いは後述)
  • サーバーストリーミングRPC(Server Streaming RPC)
    • サーバが複数のリクエストをクライアントへ送るRPCです
    • 株価のようなリアルタイムデータをクライアントに配信する場合に疲れます
  • クライアントストリーミングRPC(Client Streaming RPC)
    • クライアントが複数のリクエストをサーバへ送るRPCです
    • 動画や画像のアップロードに使われます(ファイルを小さく分割し、順次サーバーへ送信)
  • 双方向ストリーミングRPC(Bidirectional Streaming RPC)
    • 双方向でデータのやり取りを行うRPCです
    • チャットなどで使われます

参考: https://grpc.io/docs/what-is-grpc/core-concepts/

gRPCを理解する上で抑えたい基本概念

gRPCを理解する上で、理解しておきたい概念が2つあります。
それが「リモートプロシージャコール」、「プロトコルバッファ」の2つです。

リモートプロシージャコール(RPC)

リモートプロシージャコールとは、クライアントがネットワーク上のリモートサーバー上で関数やメソッドを呼び出すためのメカニズムです。

ローカルで関数を呼び出すような感覚で、リモートの関数を呼び出せます。通信の詳細はRPCフレームワークが隠蔽します。

<余談>
gRPCのgはgoogleを意味すると思っていたのですが、実はgRPCのversionごとにgの意味が異なるようです。(gの意味が機になる方は下記をチェック!)
https://github.com/grpc/grpc/blob/master/doc/g_stands_for.md

プロトコルバッファ(Protocol Buffers)

Googleが開発した言語中立・プラットフォーム中立のシリアライゼーションフォーマットです。

データを効率的にシリアライズ・デシリアライズするためのスキーマを定義します。
.protoファイルにメッセージフォーマットを定義し、それを使用してコードを自動生成します。

以下は例です。

syntax = "proto3";

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}
syntax = "proto3";

プロトコルバッファのバージョンを指定しています。

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

Greeterという名前のgRPCサービスを定義しています。
ここではSayHelloという名前のRPCメソッドを定義していて、このメソッドはHelloRequestメッセージを受け取り、HelloReplyメッセージを返します。

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

メッセージはRPCメソッドに渡すパラメータや返り値の形式を規定します。
string: フィールドのデータ型です。
nameとmessageはフィールドの名前です。

1はフィールドの番号のことで、プロトコルバッファでは各フィールドに一意の番号を割り当てます。番号はメッセージのバイナリ形式でのエンコードとデコードに使用されます。

gRPCを使った開発の流れ

次にgRPCを使った開発の流れについて解説します。
PharmaXではPythonアプリケーションとRuby on Railsアプリケーションの通信にgRPCを使っているので、PythonとRuby on RailsでgRPCのを使う想定で、サンプルコードを使って簡単に解説します。

ステップ1:.protoファイルを作成する

まず、gRPCサービスのインターフェースやメッセージフォーマットを定義する.protoファイルを作成します。このファイルは、PythonとRuby on Railsの両方で使用されます。

syntax = "proto3";

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

ステップ2:.protoをコンパイルする

# Pythonアプリケーション
protoc --proto_path=. --python_out=. --grpc_python_out=. greeter.proto
# Railsアプリケーション
protoc --proto_path=. --ruby_out=. --grpc_out=. --plugin=protoc-gen-grpc=$(which grpc_tools_ruby_protoc_plugin) greeter.proto

ステップ3:Pythonを実装

from concurrent import futures
import grpc
import greeter_pb2
import greeter_pb2_grpc

class GreeterServicer(greeter_pb2_grpc.GreeterServicer):
    def SayHello(self, request, context):
        return greeter_pb2.HelloReply(message='Hello, %s!' % request.name)

def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    greeter_pb2_grpc.add_GreeterServicer_to_server(GreeterServicer(), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    server.wait_for_termination()

if __name__ == '__main__':
    serve()

ステップ4: Railsの実装

gem 'grpc'
gem 'grpc-tools'
gem 'google-protobuf'
require 'grpc'
require 'greeter_services_pb'

module GrpcClient
  def self.say_hello(name)
    stub = Greeter::Greeter::Stub.new('localhost:50051', :this_channel_is_insecure)
    request = Greeter::HelloRequest.new(name: name)
    response = stub.say_hello(request)
    response.message
  end
end
class WelcomeController < ApplicationController
  def index
    @message = GrpcClient.say_hello('Rails User')
  end
end

簡単に流れを解説すると、以下のようになります。

  • Railsクライアントからのリクエスト:
    • クライアントはGrpcClient.say_hello('Rails User')を呼び出します。
      • このメソッド内で、gRPCのリクエストメッセージ(HelloRequest)を作成し、PythonサーバーのSayHelloメソッドを呼び出します。
  • Pythonサーバーの処理:
    • PythonサーバーはSayHelloメソッドを受け取り、リクエストの内容を処理します。
    • この例では、リクエスト内のnameフィールドの値を使って、レスポンスメッセージ(HelloReply)を作成し、messageフィールドに"Hello, Rails User!"という文字列を設定します。
  • Railsクライアントへのレスポンス:
    • RailsクライアントはPythonサーバーからのレスポンス(HelloReplyメッセージ)を受け取ります。
    • 受け取ったレスポンスメッセージ("Hello, Rails User!")のmessageフィールドの値を@messageに格納します。

PharmaXにおけるgRPCの活用事例

PharmaXでは、YOJOというオンラインで薬剤師に相談し、医薬品を購入できるtoC(顧客向け)サービスを開発しています。

このサービスでは、患者さんにメッセージを送るRailsアプリケーションと、送信するメッセージを生成するLLMを扱うPythonアプリケーションで構成されており、RailsアプリケーションとPythonアプリケーション間での通信にgRPCを使っています。

PythonではOpenAIのAPIを使って、患者さん向けのメッセージを生成したり、その前段階でそもそもメッセージを生成して良い患者さんなのかの判定をする責務を持っいて、RailsはPythonアプリケーションの判定結果をもとにメッセージを送信する責務を持っています。

簡単に言ってしまえば、Pythonが実行計画を策定し、Railsがその計画を実行するという流れになっていて、その間を取り持っているのがgRPCです。

PharmaXがgRPCを使い始めた理由

gRPCの最大の利点は、スキーマ定義がクライアントとサーバーの双方で可能なことです。これにより、インターフェースについて詳細に考慮する必要がなくなります。

前述の通り、gRPCでは、Protobufを使用してスキーマを明確に定義し、それに基づいて通信が行われるため、開発者はスキーマ駆動の設計を強制されます。これにより、最初に仕様を定め、型安全にデータのやり取りが可能となります。

また、PharmaXのようにインターナルなインターフェースとしてgRPCを使用する場合、HTTP/2をカジュアルに利用できるため、パフォーマンスの向上が期待できます。

さらに、RubyでgRPCを使った経験があるエンジニアが社内にいたことも、gRPCを選定する要因となりました。

実際にgRPCを使って感じたメリット・デメリット

まだgRPCを使い始めて日が浅いので、簡単ではありますがgRPCを使って感じたメリット・デメリットを最後に紹介します。

<メリット>
gRPCを実際に使ってみて感じたメリットとしては、まずインターフェースの作成が非常に楽であることが挙げられます。RPC設計を行うことで、REST APIのようにリソース設計をする必要がなくなります。

あと、Protobufは厳格な型を持っているため、クライアントとサーバー間でのデータ交換が型安全に行われるのも良い点です。

コードが自動生成されるのも良い点です。

<デメリット>
一方、デメリットとしては、REST APIに比べて学習コストが高いことが挙げられます。この記事でも紹介した通り、gRPCを理解する上で、知っておくべきことがけっこうあります。

また、gRPCはバイナリ形式でデータをやり取りするため、デバッグの難易度が上がる点も課題です。

gRPCはProtocol Buffers(protobuf)を使用してデータをバイナリ形式でシリアライズします。この形式は効率的ですが、直接データを見ることが難しくなります。HTTPやJSONのようなテキスト形式と異なり、バイナリデータは人間には理解しづらいものです。

たとえば、サーバーがクライアントに送信するレスポンスが意図した通りの内容になっているかを確認する場合、JSONならレスポンスのボディをそのまま見て確認できますが、gRPCではバイナリデータをデコードしなければなりません。

終わりに

以上、gRPCについて紹介しました。少しでもgRPCの理解が深まれば幸いです。

PharmaXでは、様々なバックグラウンドを持つエンジニアをお待ちしております。
もし興味をお持ちの場合は、私のXアカウント(@tomo_k09)までお気軽にメッセージをいただけますと幸いです。まずはカジュアルにお話しましょう!

<宣伝>
LLMチャット&ボイスボットのUX改善戦略というイベントやります!
すでに100名以上の方にお申し込みいただいているので、お時間ある方はぜひ〜
https://yojo.connpass.com/event/325426/

PharmaXテックブログ

Discussion