はじめに
この記事では、gRPCのgもRPCも何もわからないという方でもgRPCを理解できようにと思い書きました。できる限り丁寧な解説を目指したいと思うので、ここら辺がわかりにくかったなどがあればぜひコメントで教えてください!
弊社Nucoでは、他にも様々なお役立ち記事を公開しています。よかったら、Organizationのページも覗いてみてください。
また、Nucoでは一緒に働く仲間も募集しています!興味をお持ちいただける方は、こちらまで。
gRPCの概要
gRPCはオープンソースのRPCフレームワーク(後述)です。Googleによって開発され、2015年にオープンソースとして公開されました。gRPCは一般的にマイクロサービス間での通信や、モバイルアプリとバックエンドサーバー間の通信で用いられます。
マイクロサービスとは、機能ごとにサービスとして独立させることを指します。マイクロサービスを組み合わせることで、一つの大きなサービスを提供します。マイクロサービスの例としては、決済機能、検索機能などがあります。
(公式サイトより)
図のようにgRPCでは、異なる言語間であっても通信を行うことで、サービス連携を可能とします。例えば、gRPC サーバに実装された関数をクライアント側(図だとRubyやAndroid-Java)で実行するといったことが実現できます。
gRPC誕生の背景
gRPCはHTTP/2(後述)などの技術の標準化が進んだことで、Googleが社内用のマイクロサービスの開発基盤として使用していた高パフォーマンスのRPCフレームワーク「Stubby」を、活用して作り直し、オープンソースとして公開したのがgRPCでした。
gRPCの'g'の意味
gRPCのgはgoogleを意味しているのではなくバージョンによって意味が異なっています。
https://github.com/grpc/grpc/blob/master/doc/g_stands_for.md
gRPCの要素技術
gRPCを理解するために、gRPCの要素となる技術を取り上げて紹介します。その技術が以下の3つになります。
・RPC
・Protocol Buffers
・HTTP/2
それぞれについて説明します。
RPC(Remote Procedure Call)
RPCとは、遠隔手続き呼び出し(Remote Procedure Call)を略して、RPCと呼びます。
遠隔手続き呼び出しとは、手元のプログラム・マシン(クライアント)から要求を送信して、遠隔地にあるプログラム・マシン(サーバ)に実装されている処理を実行し、その結果をクライアントに呼び出すことを指します。ここでいう遠隔地は物理的に離れたサーバかもしれませんし、同一PC上で動作しているサーバになるかもしれません。
このため、RPCはクライアント・サーバ型の通信プロトコルであると言えます。
RPCでは、クライアント側は呼び出す方法だけを知っておけば、実行される処理などは知らなくとも結果が返ってくるという点が重要な特徴になってきます。
gRPCはこのRPCの概念を導入することで、サーバ上にある処理をクライアント上で実行することができます。
ここまで、理解するとおそらくAPIとの違いについて気になる方も出てくると思うので紹介します(自分は気になりました)。
結論から言うと、RPCはAPIの1種です。APIとはソフトウェアアプリケーション同士や異なるコンポーネント間で相互に通信するための"手段"であるため、RPCはまさにAPIの1種と言えるということです(APIを実現するための手段の1つがRPCというイメージです)。
Protocol Buffers(.proto)
gRPCでは、データのやりとりにProtocol Buffersを使用します。Protocol Buffersは言語中立的なデータフォーマットであり、異なるプログラミング言語間でのデータのやり取りを可能とするための共通のデータ表現を持ちます。類似する形式としては、JSONなどがありますが、JSONはテキスト形式であるのに対して、Protocol Buffersはバイナリ形式(情報を0と1で表す形式)に変換されるため、データ量が小さく、転送が高速で行われます。
また、gRPCでは.protoファイルを利用して、コードの自動生成を行いクライアントとサーバを実装することができます。
つまり、.protoには、通信で使用されるメッセージ、サービス、その他の関連情報が定義されています。この.protoファイルをコンパイラに通すと各言語向けのコードが生成せれ、クライアントとサーバの間で共有のメッセージフォーマットや通信機能を持つことが可能となります。
ここら辺はイメージを持つことが難しいと思うので、最初はgRPCはJSONとかじゃなくて、Protocol Buffersっていう形式を使うんだという認識でも大丈夫です。
HTTP/2
gRPCはHTTP/2上に構築されることによって高効率的な通信を可能としています。そんな、HTTP/2が何者なのか確認していきましょう。
HTTPとは
HTTP(Hypertext Transfer Protocol)は、World Wide Web上で情報をやり取りするための規格(プロトコル)です。HTTPはクライアントとサーバ間の通信を可能とし、クライアントが要求(リクエスト)を送り、それに対する応答(レスポンス)を基本として定義されます。HTTPも歴史とともに、いくつかのバージョンが出されています。ここでは、HTTP/1.1とHTTP/2を紹介します。
HTTP/1.1
HTTP/1.1は1997年に登場したHTTPのバージョンです。HTTP/1.1では、Keep Aliveとパイプライニングにより通信の高速化が実現されました。通常のHTTPでは、1回の送受信に対して、1つのコネクションが確立され、また送受信を行うためには再度、別のコネクションを立ち上げる必要があったが、年々HPが画像の多様などによりリッチになっていく中でこの方法だと、通信速度が遅くなってしまう、高負荷であるなどの問題があった。これらに対して、HTTP/1.1では、1つのコネクションで複数回の送受信を可能にしました。これがKeep Aliveと呼ばれる技術です。また、パイプラニングは、最初のリクエストの送信が完了する前に次のリクエストを送信できるようにする技術ですが、これは様々な問題から広くに使用される技術ではありませんでしたが、次に登場するHTTP/2のストリーミングと呼ばれる機能として進化を遂げます。
HTTP/2.2
HTTP/1.1ですが、様々なコンテンツが誕生したため、1回のアクセスで、大量のリクエストを送ることが当たり前になりました。そんなHTTP/1.1の限界を迎えたことで2015年に誕生したのが、HTTP/2でした。HTTP/2では1つのコネクションで複数"個"の送受信が可能となるストリーミングと呼ばれる機能が搭載されていました。また、HTTP/2ではバイナリ形式でやり取りが行われるようになりました。これらの他にもサーバプッシュ機能、ヘッダ圧縮などによりHTTP/2では従来よりも高効率で高速な通信を実現しています。
結局gRPCって・・・
これらを踏まえた上で、gRPCが結局何者なのか結論付けたいと思います。
gRPCとはGoogleによって開発されたRPCを実現するためのフレームワークです。通信プロトコルにはHTTP/2、IDLにProtocol Buffersを採用することで、データのやりとりを効率的、高速に行えるようになっており、幅広い言語に対応することで、あらゆる環境で動作可能なオープンソース。
これがgRPCと言えます。
IDLとはInterface Description Languageの略で、異なるソフトウェアコンポーネントが相互に通信するために使用されるインターフェースを定義するための言語です。IDLは通常、異なる言語やプラットフォームで開発されたソフトウェアコンポーネントが相互に通信するための共通の基盤を提供します。
gRPCの特徴
gRPCが何であるか結論づけましたが、補足が必要な箇所があると思うので、特徴として、分割しながら説明します。以下の4つがgRPCの特徴として挙げられます。
・RPCフレームワークである
・効率的
・幅広い言語に対応
・オープンソース
RPCフレームワークである
RPCは"技術の概念"であるため、それを実現するための道具が必要です。そういった意味で、gRPCはRPCを実現するための道具がセットになっているものという意味で冒頭でRPCフレームワークであると紹介を行いました。
高効率
HTTP/2上でgRPCを実現しているため、高効率にやりとりができます。Protocol Buffersのバイナリ形式はテキスト形式のJSONよりもサイズが小さいため、データ転送量の点においても高効率となっています。
幅広い言語に対応
対応言語 |
---|
C# / .NET |
C++ |
Dart |
Go |
Java |
Kotlin |
Node |
Objective-C |
PHP |
Python |
Ruby |
このように幅広い言語に対応するため、あらゆる環境で動作が可能です。
オープンソース
gRPCはソースコードがオープンになっているため、問題発生の際には原因調査が行いやすいです。実際にこちらに上がっているので興味のある方覗いてみて下さい。
gRPCの通信方法の種類
gRPCでは4つの通信方法を選択することができます。図と一緒に概要を紹介します。
Unary RPC
1つにリクエストに対して、1つのレスポンスを返すのが、Unray RPCで、よく用いられる通信方式です(REST APIと同様な方法)。
Server streaming RPC
1つのリクエストに対して、複数のレスポンスを返すのが、Server streaming RPCです。サーバからクライアントに対してサイズの大きいファイルを送信したい場合に使用します。PUSH通知を実現したい場合などにも有用です。
Client streaming RPC
複数のリクエストに対して、1つのレスポンスを返すのが、Client streaming RPCです。クライアントからサーバに対してサイズの大きいファイルを送信したい場合に使用します。ファイルアップロードなどを行いたいときにClient streaming RPCは有用です。
Bidirectional streaming RPC
複数のリクエストに対して、複数のレスポンスを返すのが、Bidirectional streaming RPCです。クライアント・サーバどちらも任意のタイミングで送信できるため、チャットやオンライン対戦などリアルタイムでの通信が必要な場合に使用されます。
ストリーム機能を用いるものに対しては、複数送信する発生元が名前になるといったイメージで覚えましょう。
REST APIとの比較
ここで、類似した技術として挙げられるREST APIを例にとって比較を行ってみましょう。
REST APIとは
REST APIはWeb APIの開発でよく使用されており、その中身は「RESTと呼ばれる設計原則に従ったAPI」これがREST APIと呼称されます。今回はあまりREST APIについて深追いはしませんが、この後の話しをわかりやすくするために、具体的なgRPCとREST APIの違いを下の表にまとめてみました。
gRPC | REST API | |
---|---|---|
HTTP規格 | HTTP/2 | HTTP/1.1 |
メッセージ形式 | Protocol Buffers | JSON or XML or その他 |
通信方式 | 紹介した4種類の内いずれか | Unary RPCと同様の方法のみ |
この違いがどのようなメリット、デメリットをもたらすのかみてみましょう。
REST APIのメリット・デメリット
メリット
・シンプルさ
RESTはシンプルであり、一般的なHTTPメソッド(GET、POST、PUT、DELETEなど)を使用しているため、実装が理解しやすいという特徴があります。
・可読性
データは一般的にJSONやXMLなどのテキスト形式で表現されるため可読性が高くなっています。
・互換性
RESTはほとんどのクライアントやサーバでサポートされているため、さまざまな環境で利用可能です。
・使用の多さ
REST APIは世界中で広く使用されているため、情報が多く、サービス統合、連携に優れています。
デメリット
・gRPCと比べてデータ通信効率に差がある
REST APIは通常、HTTP/1.1を採用しています。このプロトコルでは、基本的には1対1のリクエストとレスポンスが行われるため、データ通信効率においてgRPCのようなストリーム機能を持つプロトコルと比較すると差が生じます。大規模なデータの転送や低レイテンシの要件がある場合、gRPCの方が効率的かつ適していることがあります。
・Over-fetchingとUnder-fetching
クライアントが必要なデータを得るために、必要以上のデータを取得する「Over-fetching」や逆に必要なデータが不足している「Under-fetching」が発生することがあります。
gRPCのメリット・デメリット
メリット
・効率的な通信
gRPCはHTTP/2を使用し、バイナリプロトコルとして Protocol Buffers を採用しているため、高効率かつ低レイテンシの通信が可能となっています。RESTに比べて、メッセージ送受信は7~10倍ほど速いそうです(参考)。
・双方向通信
gRPCはストリーミングをサポートし、クライアントとサーバ間で双方向の通信が可能です。この機能は特にリアルタイム通信を行う上で必要になってきます。
・コードの自動生成
gRPCでは.protoから12の言語に対応したコードを自動で生成できるため、あらゆる言語に対応した開発が可能となっており、異なる言語間での通信を実現します。
・通信方式の豊富さ
4つの通信方式から必要に応じた方式を選べるため、用途に応じた効率的なAPI開発を行うことができます。
デメリット
・可読性の低さ
バイナリ形式のProtocol Buffersは人が直接読み取りにくいため、可読性が低くなっており、デバック難易度が上がります。
・新しい技術の学習コスト
RESTに比べて新しい技術であるため、学習コストがかかることがあります。
・情報不足
広くに採用されているわけではないため、REST APIに比べて、解説記事などの情報が少なくなっています。
・直接Webで使用することができない
gRPCは一般的にWebブラウザから直接呼び出すのは難しいです。REST APIのようにブラウザフレンドリーではありません。gRPC-webというgRPCをwebで使用するための派生形もあるので興味のある方はこちらを参考にしてみてください(今回は本筋から外れるため紹介しません)。
使い分けのポイント
基本的な使用はREST APIで十分ですが、gRPCを検討する上での基準をここでは説明します。
・外部に一般公開するかどうか
外部に一般公開する場合は、普及性、連携の観点からREST APIの方が優れています。
・リアルタイム通信を行うかどうか
オンライン対戦やリアルタイムでの配信を行いたい場合、gRPCが優れています。
・通信が限られ、効率的なデータ通信を行いたいとき
IoTのような通信環境が制限され、データの効率的なやり取りを行いたい場合はバイナリ形式であるgRPCが優れています。
gRPCチュートリアルで実際に試してみる
理論編は終わりにして、実際に手で動かして確認していきましょう。こちらのクイックスタートのPython編を試してみます。
まずgRPCに必要なツールのインストールを行います。
pip install grpcio grpc-tools
クイックスタートに必要なサンプルをダウンロードして移動します。
git clone -b v1.58.0 --depth 1 --shallow-submodules https://github.com/grpc/grpc
cd grpc/examples/python/helloworld
そうしたら、実際にサーバを起動させてみます。
python greeter_server.py
サーバ起動後、別ターミナルでクライアントを実行します。
python greeter_client.py
そうすると、以下のような結果が得られると思います。
このクイックスタートでは、名前(今回はyou)をサーバに渡して、Hello, [名前]!がレスポンスとして返ってくるようなシンプルな通信が行われています。
今度は、.protoファイルを書き換えて、.protoからコードを生成する過程とレスポンスを確認していきましょう。完成イメージとしては、先ほどの応答の下に「Hello again, you!」と表示されます。
grpc/examoles/protos/helloworld.protoを以下のように書き換えます。
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
// Sends another greeting
rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
rpc HelloStreamReply (HelloRequest) returns (stream HelloReply) {}
}
rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
この行を追加することで、定義します。
これをコンパイルすると、examples/helloworld配下にpythonコードが自動で生成されます。実際にコンパイルして確認してみましょう。
python -m grpc_tools.protoc -I../../protos --python_out=. --pyi_out=. --grpc_python_out=. ../../protos/helloworld.proto
おそらく、これを実行すると、helloworld_pb2_grpc.pyとhelloworld_pb2.pyが再生成されると思います。helloworld_pb2_grpc.pyの中身にSayHelloAgainが自動で生成されることが確認できたと思います。
最後にgreeter_client.pyとgreeter_sever.pyを以下のように書き換えて、再度実行すればOKです。
def run():
with grpc.insecure_channel('localhost:50051') as channel:
stub = helloworld_pb2_grpc.GreeterStub(channel)
response = stub.SayHello(helloworld_pb2.HelloRequest(name='you'))
print("Greeter client received: " + response.message)
response = stub.SayHelloAgain(helloworld_pb2.HelloRequest(name='you'))
print("Greeter client received: " + response.message)
class Greeter(helloworld_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
return helloworld_pb2.HelloReply(message=f"Hello, {request.name}!")
def SayHelloAgain(self, request, context):
return helloworld_pb2.HelloReply(message=f"Hello again, {request.name}!")
ここでは、protoファイルを編集することで、レスポンスの種類を増やしています。少し理解しづらいかもしれませんが、コードを書き換えて読んでいく中で、リクエストとレスポンスがなされているのがなんとなく理解できればまずは十分だと思います。
まとめ
いかがでしたでしょうか、色々詰めて説明してしまいましたが、個人的には最低限RPCがどんなものなのかだけでも拾ってもらえればなという気持ちです。これを気にgRPCについて学習してみたいという方はこちらの公式サイトを参考にしてみてください。
また、ここら辺が理解しにくかったなどあればコメント欄に教えてもらえれば、適宜修正を行いたいと思います。
最後までありがとうございました!!
弊社Nucoでは、他にも様々なお役立ち記事を公開しています。よかったら、Organizationのページも覗いてみてください。
また、Nucoでは一緒に働く仲間も募集しています!興味をお持ちいただける方は、こちらまで。