gRPCの概念、使用例、およびベストプラクティスの理解

アプリケーション開発が進むにつれて、様々な要素の中で、ある主要な点についてはあまり心配していません。それは、コンピューティングパワーです。クラウドプロバイダーの登場により、データセンターの管理について心配することが少なくなりました。すべてがオンデマンドで数秒で利用可能です。これにより、データのサイズも増加します。ビッグデータは、単一のリクエストで様々なメディアを使用して生成および転送されます。

データサイズの増加に伴い、シリアル化、デシリアル化、および輸送コストなどの活動が加わります。コンピューティングリソースについては心配していませんが、遅延がオーバーヘッドとなります。輸送を削減する必要があります。これまでに多くのメッセージングプロトコルが開発されてきました。SOAPは大きく、RESTはシンプルなバージョンですが、さらに効率的なフレームワークが必要です。そこで登場するのが、リモートプロシージャコール(RPC)です。

この記事では、RPCとは何か、そしてRPCのさまざまな実装、特にGoogleのRPC実装であるgRPCに焦点を当てて理解します。また、RESTとRPCを比較し、gRPCのさまざまな側面、包括安全性、ツール、そしてさらに多くを理解します。

RPCとは何ですか?

RPCはリモートプロシージャコールの略です。定義は名前の通りです。プロシージャコールは単に関数/メソッドコールを意味します。”リモート”という言葉が大きな違いを生み出します。リモートで関数コールを行うことができたらどうでしょうか?

簡単に言うと、関数がサーバーに存在し、クライアント側から呼び出すために、メソッド/関数の呼び出しとして簡単にできるでしょうか?実際、RPCが行うことは、クライアントにそれがローカルメソッドを呼び出しているように見せかけることですが、実際には、ネットワーク層のタスクを抽象化したリモートマシンのメソッドを呼び出しています。この美しさは、契約が非常に厳格で透明であることです(これについては後の記事で説明します)。

RPC呼び出しに関与するステップ:

RPCシーケンスフロー

これが典型的なRESTプロセスの様子です:

RPCはプロセスを以下のように簡略化します:

これは、リクエストを行う際に伴う複雑さが私たちから抽象化されているためです(これについてはコード生成で説明します)。私たちが心配する必要があるのはデータとロジックだけです。

gRPC: 何、なぜ、そしてどのように

これまでRPCについて議論してきましたが、それは基本的にリモートで関数/メソッド呼び出しを行うことを意味し、”厳格な契約定義“、”データの送受信と変換の抽象化”、”レイテンシーの低減”などの利点をもたらします。これからこの投稿を進めて議論していきます。私たちが本当に深く掘り下げたいのは、RPCの実装の一つです。RPCは概念であり、gRPCはそれをベースとしたフレームワークです。

RPCの実装はいくつかあります。それらは:

  • gRPC (google)

  • Thrift (Facebook)

  • Finalge (Twitter)

GoogleのRPCバージョンはgRPCと呼ばれています。2015年に導入され、その後注目を集めています。マイクロサービスアーキテクチャで最も選ばれる通信メカニズムの一つです。

gRPCはプロトコルバッファ(オープンソースのメッセージ形式)をクライアントとサーバーの間の通信のデフォルト方法として使用しています。また、gRPCはHTTP/2をデフォルトプロトコルとして使用しています。gRPCがサポートする通信の種類は次の4つです:

gRPCで広く使用されているメッセージ形式について説明します。それはプロトコルバッファ、いわゆるprotobufsです。protobufのメッセージは以下のような形式を取ります。

message Person {
string name = 1;
string id = 2;
string email = 3;
}

ここで、’Person’は転送したいメッセージ(リクエスト/レスポンスの一部として)であり、フィールド’name’(文字列型)、’id’(文字列型)、’email’(文字列型)を持っています。数字1,2,3は、データがバイナリ形式にシリアル化される際の位置を表します(例えば、’name’、’id’、’has_ponycopter’など)。

開発者がメッセージを含むProtocol Bufferファイルを作成した後、Protocol Bufferコンパイラ(バイナリ)を使用して書かれたProtocol Bufferファイルをコンパイルできます。これにより、メッセージで作業するために必要なすべてのユーティリティクラスとメソッドが生成されます。例えば、ここに示すように、生成されたコード(選択した言語に応じて)はこのようになります

サービスをどのように定義するのか?

上記のメッセージを送受信するために使用するサービスを定義する必要があります。

必要なリクエストおよびレスポンスメッセージの型を書き終えた後、次のステップはサービス自体を書くことです。

gRPCサービスもProtocol Buffersで定義され、「service」および「RPC」キーワードを使用してサービスを定義します。

以下のprotoファイルの内容を見てみましょう:

message HelloRequest {
string name = 1;
string description = 2;
int32 id = 3;
}

message HelloResponse {
string processedMessage = 1;
}

service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}

ここで、HelloRequestとHelloResponseはメッセージであり、HelloServiceはHelloRequestを入力とし、HelloResponseを出力とするunary RPCであるSayHelloを1つ公開しています。

前述の通り、現在のHelloServiceは単一の単項RPCを含んでいますが、複数のRPCを含むことも可能です。また、さまざまなタイプのRPC(単項/クライアント側ストリーミング/サーバ側ストリーミング/双方向)を含むこともできます。

ストリーミングRPCを定義するためには、リクエスト/レスポンス引数の前に「stream 」を付けるだけです。ストリーミングRPCのproto定義と生成されたコード.

上記のコードベースリンク:

gRPC Vs. REST

gRPCについてはかなり話をしましたが、RESTについても触れました。しかし、その違いについては議論していませんでした。つまり、RESTという軽量で確立された通信フレームワークがあるのに、なぜ別の通信フレームワークを探す必要があったのでしょうか? RESTとの関連でgRPCについてもっと理解するとともに、それぞれの利点と欠点について説明しましょう。

比較するためにはパラメータが必要です。そこで、比較を以下のパラメータに分けて考えましょう:

  • メッセージ形式: Protocol Buffers 対 JSON

    • シリアル化およびデシリアル化の速度は、すべてのデータサイズ(小/中/大)においてProtocol Buffersの方が大幅に優れています。ベンチマークテスト結果

    • シリアル化後のJSONは人間が読めるのに対し、Protocol Buffers(バイナリ形式)はそうではありません。これが欠点かどうかはわかりませんが、時にはGoogle開発者ツールやKafkaトピックでリクエストの詳細を見たい場合があり、Protocol Buffersの場合は何も理解できません。

  • 通信プロトコル:HTTP 1.1対HTTP/2T

    • RESTはHTTP 1.1に基づいており、RESTクライアントとサーバ間の通信には確立されたTCP接続が必要で、それには3ウェイハンドシェイクが関与します。クライアントからリクエストを送信してサーバからの応答を受けると、その後はTCP接続は存在しません。別のリクエストを処理するために新しいTCP接続を作成する必要があります。各リクエストごとにTCP接続の確立が加算され、遅延が増加します。

    • そこでgRPCはHTTP 2に基づいており、この課題に対処するために永続的な接続を持っています。永続的な接続は、TCP接続が乗っ取られ、データ転送が監視されていないウェブソケットとは異なることを覚えておく必要があります。gRPC接続では、TCP接続が確立されると、複数のリクエストにわたって再利用されます。同じクライアントとサーバペアからのすべてのリクエストは、同じTCP接続に多重化されます。

  • データとロジックだけを心配すればよい:コード生成が第一級市民

    • コード生成機能は、gRPCの組み込みprotocコンパイラを通じてgRPCに組み込まれています。REST APIの場合、Swaggerなどの第三者ツールを使用して、さまざまな言語でAPI呼び出しのコードを自動生成する必要があります。

    • gRPCの場合、マーシャリング/アンマーシャリング、接続の設定、メッセージの送受信のプロセスを抽象化しています。私たちが心配する必要があるのは、送信または受信したいデータとロジックだけです。

  • 伝送速度

    • バイナリ形式はJSON形式よりもはるかに軽量なので、gRPCの場合、伝送速度はRESTよりも7倍から10倍速くなります。

機能

REST

gRPC

通信プロトコル

リクエスト-レスポンスモデルに従います。HTTPのバージョンに関係なく動作しますが、一般的にHTTP 1.1で使用されます

クライアント-レスポンスモデルに従い、HTTP 2を基盤としています。一部のサーバーでは、HTTP 1.1で動作するようにするための回避策があります(ゲートウェイ経由)

ブラウザサポート

どこでも動作

限定されたサポート。gRPC-Webを使用する必要があります。これはウェブ用の拡張機能で、HTTP 1.1に基づいています

ペイロードデータ構造

データ伝送にはほとんどJSONとXMLベースのペイロードを使用します

デフォルトでペイロードを伝送するためにプロトコルバッファを使用します

コード生成

クライアントコードを生成するためにSwaggerなどのサードパーティツールを使用する必要があります

gRPCはさまざまな言語のためのコード生成に対してネイティブサポートを持っています

リクエストキャッシング

クライアント側およびサーバ側でリクエストを簡単にキャッシュできます。ほとんどのクライアント/サーバはそれをネイティブにサポートしています(たとえばクッキー経由で)

デフォルトではリクエスト/レスポンスのキャッシングをサポートしていません

再び、現時点ではgRPCはブラウザサポートを持っていません。なぜならほとんどのUIフレームワークはまだgRPCに対するサポートが限定されているか、まったくないからです。gRPCは内部のマイクロサービス通信において自動的な選択肢である場合が多いですが、UI統合を必要とする外部通信においては同じではありません。

これで、gRPCとRESTの両方のフレームワークを比較しました。どちらを使用し、いつ使用するか?

  • 軽量なマイクロサービスが複数あるマイクロサービスアーキテクチャで、データ伝送の効率が最重要である場合、gRPCは理想的な選択肢です。

  • 複数言語サポートのコード生成が要件であれば、gRPCは選択すべきフレームワークです。

  • gRPCのストリーミング機能を活用すれば、取引やOTTなどのリアルタイムアプリは、RESTを使ったポーリングよりも恩恵を受けることができます。

  • 帯域幅が制約されている場合、gRPCはより低いレイテンシと高いスループットを提供します。

  • 開発が迅速で高速な反復が要求される場合、RESTは選択すべきオプションです。

gRPCの概念

負荷分散

永続的な接続はレイテンシの問題を解決するものの、負荷分散という別の課題を引き起こします。gRPC(またはHTTP2)が永続的な接続を作成するため、ロードバランサーが存在しても、クライアントはロードバランサーの背後にあるサーバーと永続的な接続を形成します。これはスティッキーセッションに例えることができます。

デモを通じて問題や課題を理解することができます。コードおよびデプロイメントファイルは以下の場所にあります: https://github.com/infracloudio/grpc-blog/tree/master/grpc-loadbalancing.

上記のデモコードベースから、負荷分散の責任がクライアントにあることがわかります。これは、gRPCの利点である永続的な接続がこの変更で存在しないことを意味します。しかし、gRPCは他の利点のために依然として使用できます。

gRPCにおける負荷分散についてもっと読む。

上記のデモコードベースでは、ラウンドロビン負荷分散戦略のみが使用されています/紹介されています。しかし、gRPCは別のクライアントベースの負荷分散戦略OOBである「ピックファースト」もサポートしています。

さらに、カスタムクライアント側の負荷分散もサポートされています。

クリーンコントラクト

RESTでは、クライアントとサーバーの間のコントラクトは文書化されていますが厳密ではありません。もっと前のSOAPに戻ると、コントラクトはwsdlファイルを通じて公開されていました。RESTでは、Swaggerやその他の手段を通じてコントラクトを公開します。しかし、厳密さは不足しており、クライアントコードが開発されている間にサーバー側のコントラクトが変更されたかどうかを確実に知ることはできません。

gRPCでは、プロトコルバイトファイルから生成されたスタブを介して契約がクライアントとサーバーの両方に共有されます。これは、関数呼び出しをリモートで行うのと同じです。関数呼び出しを行っているため、何を送る必要があるのか、そして何を応答として期待するのかが明確です。クライアントとの接続の複雑さ、セキュリティの管理、シリアル化-デシリアル化などは抽象化されています。私たちが気にするのはデータだけです。

以下のコードベースを考えてみてください:

https://github.com/infracloudio/grpc-blog/tree/master/greet_app     

クライアントはスタブ(プロトコルファイルから生成されたコード)を使用してクライアントオブジェクトを作成し、リモート関数呼び出しを実行します: 

 

```sh

import greetpb "github.com/infracloudio/grpc-blog/greet_app/internal/pkg/proto"



cc, err := grpc.Dial(“<server-address>”, opts)

if err != nil {

    log.Fatalf("could not connect: %v", err)

}



c := greetpb.NewGreetServiceClient(cc)



res, err := c.Greet(context.Background(), req)

if err != nil {

    log.Fatalf("error while calling greet rpc : %v", err)

}

```

同様に、サーバーも同じスタブ(プロトコルファイルから生成されたコード)を使用してリクエストオブジェクトを受信し、応答オブジェクトを作成します: 

 

```sh

import greetpb "github.com/infracloudio/grpc-blog/greet_app/internal/pkg/proto"



func (*server) Greet(_ context.Context, req *greetpb.GreetingRequest) (*greetpb.GreetingResponse, error) {

 

  // 'req'で何かを行う

 

   return &greetpb.GreetingResponse{

    Result: result,

      }, nil

}

```

両方とも、プロトコルファイルから生成された同じスタブをここに置いて使用しています。

そして、スタブは以下のプロトコルコンパイラのコマンドを使用して生成されました。

 

```sh

protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative internal/pkg/proto/*.proto

```

セキュリティ

gRPCの認証と承認は、2つのレベルで機能します:

  • コールレベルの認証/承認は通常、コールが行われる際にメタデータに適用されるトークンを通じて処理されます。 トークンベースの認証例.

  • チャネルレベルの認証は、接続レベルで適用されるクライアント証明書を使用します。また、チャネル上のすべてのコールに自動的に適用されるコールレベルの認証/承認資格情報も含めることができます。 証明書ベースの認証例.

これらのメカニズムのいずれかまたは両方を使用してサービスをセキュアにすることができます。

ミドルウェア

RESTでは、ミドルウェアを様々な目的で使用します。例えば:

  • レートリミティング

  • リクエスト/レスポンスの事前/事後検証

  • セキュリティの脅威に対処

gRPCでも同様のことが可能です。gRPCでの表現は異なり、インターセプターと呼ばれますが、同様の活動を行います。

‘greet_app’のミドルウェアブランチにおいて、ロガーとPrometheusインターセプターを統合しています。

インターセプターがPrometheusとロギングパッケージをどのように設定されているかをこちらで確認できます。

ただし、他のパッケージをインターセプターに統合して、パニックの防止や復旧(例外処理)、トレーシング、認証などの目的に対応することもできます。

gRPCフレームワークでサポートされているミドルウェア.

Protoファイルのパッケージング、バージョン管理、およびコーディング規則

パッケージング

では、パッケージングブランチに従って進めましょう。

まず最初に、‘Taskfile.yaml’から始め、タスク‘gen-pkg’にあるように、‘protoc –proto_path=packaging packaging/*.proto –go_out=packaging’と記述されています。これは、‘protoc’(コンパイラ)が‘packaging/*.proto’内のすべてのファイルを、‘–go_out=packaging’フラグによって示されたように、‘packaging’ディレクトリ内でその対応する‘go’ファイルに変換することを意味します。

次に、‘processor.proto’ファイルでは、‘CPU’と‘GPU’の2つのメッセージが定義されています。CPUは3つの組み込みデータ型のフィールドを持つ単純なメッセージですが、一方のGPUは‘Memory’と呼ばれるカスタムデータ型を持ちます。‘Memory’は別のメッセージであり、完全に別のファイルで定義されています。

では、‘processor.proto’ファイルで‘Memory’メッセージをどのように使用するのでしょうか?importを使用します。

たとえimportを明示してタスク‘gen-pkg’を実行してプロトコルファイルを生成しようとしても、エラーが発生します。デフォルトで‘protoc’は‘memory.proto’と‘processor.proto’の両方のファイルが別々のパッケージにあると想定しているためです。そのため、両方のファイルで同じパッケージ名を指定する必要があります。

オプションの‘go_package’は、コンパイラに対して‘go’ファイルのパッケージ名を‘pb’として作成するよう指示します。他の言語のプロトコルファイルが作成される場合、パッケージ名は‘laptop_pkg’となります。

バージョニング

  • gRPCには、破壊的変更と非破壊的変更という2種類の変更があります。

  • 非破壊的変更には、新しいサービスの追加、サービスへの新しいメソッドの追加、リクエストまたはレスポンスプロトコルへのフィールドの追加、および列挙型への値の追加が含まれます。

  • フィールドの名前変更、フィールドのデータ型の変更、フィールド番号の変更、パッケージ、サービス、メソッドの名前変更または削除などの破壊的変更は、サービスのバージョニングを必要とします。

  • オプションパッケージング。

コードのベストプラクティス

  • リクエストメッセージはリクエストでサフィックスする必要があります`CreateUserRequest`

  • レスポンスメッセージはリクエストでサフィックスする必要があります`CreateUserResponse`

  • レスポンスメッセージが空の場合、空のオブジェクト`CreateUserResponse`を使用するか、`google.protobuf.Empty`を使用できます。

  • パッケージ名は意味をなし、バージョン管理されている必要があります。例: パッケージ`com.ic.internal_api.service1.v1`

ツール

gRPCエコシステムは、開発以外のタスク(ドキュメント、gRPCサーバーのRESTゲートウェイ、カスタムバリデータの統合、リンティングなど)で生活を便利にするための様々なツールをサポートしています。以下は、同じ目的を達成するために役立ついくつかのツールです。

  • protoc-gen-grpc-gateway — gRPC REST APIゲートウェイを作成するプラグイン。これにより、gRPCエンドポイントをREST APIエンドポイントとして使用でき、JSONからprotoへの変換を実行します。基本的に、カスタムアノテーションを使用してgRPCサービスを定義し、それらのgRPCメソッドをRESTを使用してJSONリクエストでアクセス可能にします。

  • protoc-gen-swagger — grpc-gateway 用の補助プラグインで、gRPC ゲートウェイに必要なカスタムアノテーションに基づいて swagger.json を生成できます。そのファイルをお好みの REST クライアント(例えば Postmanなど)にインポートし、公開したメソッドに対して REST API 呼び出しを実行できます。

  • protoc-gen-grpc-web — フロントエンドがバックエンドと gRPC コールを使用して通信できるようにするプラグインです。このトピックについては、将来別途ブログ記事で取り上げます。

  • protoc-gen-go-validators — これは、protoメッセージのフィールドに対する検証ルールを定義できるプラグインです。GoLangで呼び出せるValidate()エラーメソッドを生成し、メッセージが事前に定義された期待に一致するかを検証できます。

  • https://github.com/yoheimuta/protolint — protoファイルにリントルールを追加するプラグインです。

POSTMANでのテスト

REST APIをPostmanやInsomniaのような同等のツールでテストするのとは異なり、gRPCサービスをテストするのはそれほど快適ではありません。

注:gRPCサービスは、evans-cliなどのツールを使用してCLIからもテストできます。ただし、反射が有効になっていない場合(protoファイルへのパスが必要)、gRPCサーバーで反射を有効にする必要があります。反射を有効にするために行う変更と、evans-cliのreplモードに入る方法。replモードに入った後、CLI自体からgRPCサービスをテストでき、そのプロセスはevans-cliのGitHubページに記載されています。

PostmanはgRPCサービスをテストするベータ版を持っています。

以下はその方法の手順です:

  1. Postmanを開く

  2. 左サイドバーの‘APIs’に移動

    

  1. 新しいAPIを作成するために‘+’記号をクリック

    

  1. ポップアップウィンドウで、‘名前’、‘バージョン’、‘スキーマ詳細’を入力し、作成をクリックします[GitHub/Bitbucketなどのソースからインポートする必要がない限り]。このステップは、proto契約をコピーして貼り付ける場合に関連しています。

5. 以下に示すように、APIが作成されます。バージョン「1.0.0」をクリックし、定義に移動して、あなたのproto契約を入力してください。

  1. ここでのインポートは機能しないため、すべての依存関係のあるprotoを1つの場所に保管する方が良いでしょう。

  2. 上記の手順により、将来使用するための契約を保持することができます。

  3. 次に、「新規」をクリックし、「gRPCリクエスト」を選択してください。

  1. URIを入力し、保存されたAPIのリストからprotoを選択してください。

    

  1. リクエストメッセージを入力し、「Invoke」をクリックしてください。

上記の手順で、POSTMANを使用してgRPC APIをテストするプロセスを理解しました。gRPCエンドポイントをテストするプロセスは、POSTMANを使用してRESTエンドポイントをテストするプロセスとは異なります。覚えておいてほしいのは、#5でproto契約を作成して保存する際には、すべてのprotoメッセージとサービス定義を同じ場所に配置する必要があるということです。POSTMANでは、protoメッセージをバージョン間でアクセスする機能がありません。

結論

この投稿では、RPCについて理解を深め、RESTとの類似点と相違点を議論し、その後Googleによって開発されたRPCの実装、すなわちgRPCについて議論しました。

gRPCは、特にマイクロサービスベースのアーキテクチャの内部通信において、重要なフレームワークです。外部通信にも使用できますが、RESTゲートウェイが必要です。gRPCは、ストリーミングおよびリアルタイムアプリケーションに必須です。

Golangがサーバーサイドスクリプティング言語としてどれほど証明されているかのように、gRPCは事実上の通信フレームワークとして証明されています。

Source:
https://dzone.com/articles/understanding-grpc-concepts-use-cases-amp-best-pra