gRPC and REST with gRPC in practice

gRPC and REST with gRPC in practice

59c0cc69a8ad4ca8d26d752b3b795b55?s=128

kazegusuri

June 15, 2017
Tweet

Transcript

  1. gRPC and REST with gRPC in practice API Meetup Tech

    Deep Dive #1: gRPC Masahiro Sano (@kazegusuri)
  2. 自己紹介 • 佐野 正浩(@kazegusuri) • Mercari, Inc → Souzoh, Inc

    (出向) • Principal Engineer • Platform Team • GoとPHPで開発しています 2
  3. Agenda • gRPCの概要 • gRPCの使い方 • REST と Browser •

    gRPCの新機能 • メルカリでの事例 • まとめ 3
  4. gRPCの概要

  5. gRPCとは • gRPC Remote Procedure Call • HTTP/2を使ったRPCフレームワーク • 汎用的で高性能

    • 現代的な機能 • マイクロサービスに適している • 2015年2月にGoogleが公開 • 2016年8月にGA • 現在 v1.3 • Cloud Native Computing Foundationのプロジェクト • サービス定義 • 提供するメソッド • リクエスト/レスポンスの構造 • デフォルトでProtocol Buffersを利用 5
  6. Protocol Buffers V3 • 強い型付け • 効率的なバイナリデータ形式 • JSONマッピング •

    IDL(Interface Definition Language)による記述 • サービス定義 • RPCシステム(gRPC)向けのインタフェイス定義 • 複数の言語に対応したコード生成 • メッセージ型 • クライアントとサーバの両方 6
  7. protoファイルの例 syntax = "proto3"; package search; service SearchService { rpc

    Search (SearchRequest) returns (SearchResponse); } message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; } message SearchResponse { ... } 7
  8. protoファイルの例 syntax = "proto3"; package search; service SearchService { rpc

    Search (SearchRequest) returns (SearchResponse); } message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; } message SearchResponse { ... } サービス定義 メソッド定義 リクエスト メッセージ レスポンス メッセージ メッセージ 定義 8
  9. gRPCの特徴 • 単方向とストリーミングを含む4つのRPC • 単一コネクションと通信の多重化 • RPCの終了 • キャンセル •

    タイムアウト & デッドライン • ステータスコード • メタデータ • メッセージフォーマット非依存 9
  10. Unary RPC • 送受信が各1回ずつのRPC Client Server Request Response 10

  11. Client streaming RPC • クライアントからのストリーミングリクエスト • リクエストを複数繰り返す • サーバからのレスポンスは最後の1回のみ Client

    Server Request Response … 11
  12. Server streaming RPC • サーバからのストリーミングレスポンスが可能なRPC • クライアントからリクエストを1回送信 • サーバからのレスポンスを複数回繰り返す •

    必ずクライアントのリクエストから開始 Client Server Request Response … 12
  13. Bidirectional streaming RPC • 双方向のストリーミング • リクエストとレスポンスを複数回繰り返す • 回数や送受信の順番も自由 •

    必ずクライアントのリクエストから開始 Client Server Request Response … 13
  14. 永続コネクションと多重化 • サーバへは1つのコネクションを張りっぱなし • 複数のリクエスト・レスポンスを同時に処理 • HTTP/2の多重化を活用 • 都度切断したりレスポンス待ちにはならない 14

  15. RPCの終了 • クライアントとサーバは独立してRPCを終了できる • 片方が成功、片方が失敗も許容 • RPCのキャンセル • 双方がいつでも即時キャンセル可能 •

    デッドラインとタイムアウト • クライアントはRPC完了を待つ時間を指定可能 • サーバ側ではRPCがタイムアウトを確認可能 15
  16. ステータスコード • gRPC独自のステータスコード • HTTPステータスコードを直接利用しない • ステータスでエラーハンドリングを明確に • 例 •

    OK • InvalidArgument • NotFound • AlreadyExists • Unauthenticated • Unavailable • DeadlineExceeded • Internal 16
  17. メタデータ • RPCに対する付加情報 • 認証のためのクレデンシャル • エラーに対する詳細情報 • Key/Valueペアのリスト •

    リクエストとレスポンスそれぞれに付与できる • HTTPにおけるヘッダーのようなもの 17
  18. メッセージフォーマット非依存 • gRPCは通信方式の仕様のみ • Protocol Buffersがデフォルトで採用 • 他のエンコーディングで置き換え可能 • JSON,

    FlatBuffers, XML, Thrift… 18
  19. サポート言語 • 主要な言語は対応済み • 完成度や機能性は言語毎に違う • C core base •

    C++, C#, Objective-C • Ruby, Python, PHP, NodeJS • Swift, Haskell (experimental) • grpc-java • Java, Android Java • grpc-go 19
  20. gRPCの使い方

  21. gRPCの使い方 • サーバをGo、クライアントをRubyで実装 • サーバ実装 1. proto定義を書く 2. protoからコードを生成 3.

    サービスを実装 4. gRPCサーバを起動 • クライアント実装 5. protoからコードを生成 6. クライアント実装 21
  22. proto定義を書く • EchoServiceを作成 • Echoメソッドのみを持つ • リクエストとレスポンスはEchoMessage型 syntax = "proto3";

    package echo; service EchoService { rpc Echo(EchoMessage) returns (EchoMessage); } message EchoMessage{ string msg = 1; } 22
  23. protoからGoコードを生成 • Messageはstructに、serviceはinterfaceに • クライアントとサーバーの両方の定義が生成される type EchoServiceClient interface { Echo

    (ctx context.Context, in *EchoMessage, opts ...grpc.CallOption) (*EchoMessage, error) } type EchoServiceServer interface { Echo (context.Context, *EchoMessage) (*EchoMessage, error) } type EchoMessage struct { Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"` } $ protoc --go_out=plugins=grpc:. echo.proto echo.proto 23
  24. サービスを実装する • Echo関数を実装 • エラーは専用のステータスを返す type EchoServer struct {} func

    (s *EchoServer) Echo( ctx context.Context, in *pb.EchoMessage) (*pb.EchoMessage, error) { if in.Msg == “” { return nil, grpc.Errorf(codes.InvalidArgument, “message is empty”) } return &pb.EchoMessage{Msg: in.Msg}, nil } echo.pb.go 24
  25. gRPCサーバを起動 • 実装したサービスを登録してサーバを起動 func main() { s := grpc.NewServer() echoServer

    := &EchoServer{} pb.RegisterEchoServiceServer(s, echoServer) lis, _ := net.Listen("tcp", ":8080") s.Serve(lis) } $ go run main.go main.go 25
  26. protoからRubyコードを生成 module Echo module EchoService class Service …. rpc :Echo,

    EchoMessage, EchoMessage end Stub = Service.rpc_stub_class end end module Echo EchoMessage = Google::Protobuf::DescriptorPool. generated_pool.lookup("echo.EchoMessage").msgclass end $ grpc_tools_ruby_protoc -I. --ruby_out=. --grpc_out=. echo.proto echo_pb.rb echo_services_pb.rb 26
  27. クライアント実装 • サーバに接続してメソッドを呼び出すだけ • 実質2行 require_relative './echo_services_pb.rb' stub = Echo::EchoService::Stub.new('localhost:8080',

    :this_channel_is_insecure) message = ARGV[0] response = stub.echo(Echo::EchoMessage.new(msg: message)).msg p “Echo: #{response}" $ ruby -I. client.rb client.rb 27
  28. REST と Browser

  29. REST APIは終わった? • gRPCとprotobufで簡単に高性能な実装が可能 • REST APIはもはや必要ない…? • まだREST APIが必要な場所は多い

    • 後方互換性のため • gRPCサポートが未成熟な言語向け • ウェブブラウザ • 一度慣れるとRESTに戻りたくない… 29
  30. grpc-gateway • RESTful JSON APIからgRPCへのリバースプロキシ • Go実装 • protobufのサービス定義からコード生成 •

    Cloud Endpoints for gRPCのOSS版のようなもの https://github.com/grpc-ecosystem/grpc-gatewayより 30
  31. マッピングルールの記述 • optionでメッセージへのマッピングを定義 service EchoService { rpc Echo(EchoMessage) returns (EchoMessage);

    } service EchoService { rpc Echo(EchoMessage) returns (EchoMessage) { option (google.api.http) = { post: “/v1/echo” body: “*” }; } } 31
  32. Gatewayのコード生成と起動 • protoからgrpc-gatewayのコード生成 • 生成されたコードを利用してプロキシサーバを起動 • GatewayがPort 8081, gRPCがPort 8080

    $ protoc -I. --grpc-gateway_out=. echo.proto $ go run gateway.go func main() { ctx := context.Background() mux := runtime.NewServeMux() opts := []grpc.DialOption{grpc.WithInsecure()} err := pb.RegisterEchoServiceHandlerFromEndpoint ( ctx, mux, "localhost:8080", opts) http.ListenAndServe("localhost:8081", mux) } gateway.go 32
  33. マッピングルール • Bodyをメッセージにマッピング HTTP RPC POST /v1/echo {“msg”: “hello”} Echo(msg:

    “hello”) service EchoService { rpc Echo(EchoMessage) returns (EchoMessage) { option (google.api.http) = { post: “/v1/echo” body: “*” }; } } 33
  34. マッピングルール • パラメータをメッセージにマッピング HTTP RPC GET /v1/echo?msg=hello Echo(msg: “hello”) service

    EchoService { rpc Echo(EchoMessage) returns (EchoMessage) { option (google.api.http) = { get: “/v1/echo” }; } } 34
  35. マッピングルール • パスをメッセージにマッピング HTTP RPC GET /v1/echo?msg=hello Echo(msg: “hello”) GET

    /v2/echo/hello Echo(msg: “hello”) service EchoService { rpc Echo(EchoMessage) returns (EchoMessage) { option (google.api.http) = { get: “/v1/echo” additional_bindings { get : "/v2/echo/{msg}" } }; } } 35
  36. Swagger (OpenAPI) • RESTful APIの仕様を記述するための規格 • その周辺ツール • Swagger SpecがそのままOpenAPI

    Specに • grpc-gateway用のproto定義から自動生成可能 $ protoc -I. --swagger_out=. echo.proto 36
  37. 37

  38. gRPCのブラウザ対応 • 一部のブラウザしか対応していない機能がある • HTTP/2, raw binary, Trailer • 特にTrailer

    • grpc-gateway • RESTful JSON APIからgRPCへのプロキシ • gRPC-Web • gRPC protocolをブラウザ向けに拡張 • github.com/improbable-eng/grpc-web 38
  39. gRPC-Web • Improvableが公開 • その実装をもとにgRPC-Web protocolが出来た • 3つのコンポーネント • grpcweb

    • gRPCサーバのラッパー • ts-protoc-gen • TypeScript/JavaScript用のprotocプラグイン • grpc-web-client • TypeScript/JavaScript用のgRPCライブラリ 39
  40. grpcweb • gRPCサーバをgRPC-Web protocolでラップ • CORSプリフライトとprotocol変換 • Go実装 • Go以外のgRPCサーバ向けにプロキシもある

    • Trailerをヘッダーやペイロードで表現しなおす • 実装量は少ないがやっていることはハック 40
  41. grpc-web-client • ブラウザ向けgRPC-Webクライアント • ts-protoc-genで生成されたコードから利用される • TypeScript/JavaScript • FetchAPIを利用 •

    未対応の場合はXMLHTTPRequestを利用 41
  42. ステータス • アルファ版 • Unary RPC, Server streaming RPCのみ対応 •

    Streams APIが入れば変わるかも • 仕様自体もまだ議論中 42
  43. gRPCの新機能

  44. gRPCの新機能 • Interceptor • Status message • Server Reflection •

    Load Balance 44
  45. Interceptor • WAFでのミドルウェアのような仕組み • 以前は仕組み自体がなかったため大変だった • RPCのメソッド呼び出し時に共通処理を挟み込める • 認証、ログ、メトリクス計測など •

    クライアント側とサーバ側の両方で利用可能 • 最近はこれを利用したミドルウェアが増えてきた 45
  46. エラー時の詳細情報 • gRPCではエラー時のベストプラクティスがなかった • ステータスコードとメッセージしか返せない • メタデータで返す…? • そもそもエラーにせず全部OKで返す •

    メタデータで返すのが推奨とあるがあまり例を見ない 46
  47. Status message • エラーをStatusメッセージで表現することが可能に • detailsがAny型なのでどんな値も格納可能 • 統一した詳細なエラー表現が可能に • 実装としてはメタデータにprotobufをそのまま格納

    package google.rpc; message Status { int32 code = 1; string message = 2 repeated google.protobuf.Any details = 3; } 47
  48. Server Reflection • サーバと通信するにはサービス定義が必要 • クライアントに事前に配布する必要がある • Server Reflection •

    サービス定義を返すためのサービス • 現状はProtocol Buffersに依存 • デバッグツールなどにも便利 48
  49. ロードバランシング • 永続接続の負荷分散は難しい • 接続した場所にリクエストを続ける • クライアントにより偏りがある • TCPロードバランサー •

    Protocolに依存しない • Per-Connection • バックエンドの接続先の振り分けがしづらい 49
  50. gRPCのロードバランシング • Per-Call(RPC) ロードバランシング • クライアント側でのロードバランス • バランサーを通すか通さないかの2種類のポリシー • backend(ラウンドロビン)

    • grpclb • 仕組みとしては2段階 • 名前解決(naming) • ロードバランシング • round_robin, grpclb 50
  51. 名前解決(naming) • 接続先情報を取得 • デフォルトがDNS • SRVレコードで詳細なデータも返せる • APIで独自の実装が可能 •

    接続先情報は3つ • アドレス • IPアドレス, ポート番号 • バランサータイプ • grpclbかどうか • サーバ名 • (SSLなどの)認証用 51
  52. ロードバランス(round robin) • 名前解決で直接バックエンドサーバを取得 • クライアント側で分散する • 各サーバにコネクションを張りっぱなし Name Resolver

    gRPC Client gRPC Server gRPC Server gRPC Server 52
  53. ロードバランス(grpclb) • 名前解決でバランサーを取得 • バランサーから動的にバックエンドサーバを更新 • クライアント側で分散 • 各サーバにコネクションを張りっぱなし Name

    Resolver gRPC Client gRPC Server gRPC Server gRPC Server Load Balancer 53
  54. HTTP CONNECTプロキシ • Proxyを通してバックエンドに接続したい • CONNECTメソッドで接続先を指定 • Resolverがプロキシのアドレスを返す Name Resolver

    gRPC Client gRPC Server gRPC Server gRPC Server Proxy 192.168.10.1 192.168.10.2 192.168.10.3 10.0.0.1, grpclb, 192.168.10.1 10.0.0.1, grpclb, 192.168.10.2 10.0.0.1, grpclb, 192.168.10.3 10.0.0.1 CONNECT 192.168.10.1 54
  55. メルカリでの事例

  56. なぜメルカリでgRPCを採用するか • 新規マイクロサービス作成コスト • 複数のサービス+複数の言語 • Strongly Typed • JSONよりもパフォーマンスが良い

    • Single Source of Truth • 最新のサービス定義 • 全てのサービスの定義をまとめて管理 • 全てをgRPCで統一している訳ではない 56
  57. メルカリでの事例: プラットフォーム • プラットフォームとマイクロサービス化 • モノリシックからマイクロサービスへ • メルカリ アッテやメルカリ カウルとの連携

    • プラットフォームは全てgRPC+Goで実装 • 複数言語向けのクライアント提供 • サービス内のコンポーネント間通信もgRPC 57
  58. メルカリでの事例: クライアントアプリ • iOS/AndroidからgRPCで直接APIと通信にトライ • Androidで問題が出て断念 • 2016年8月頃 • gRPC

    1.0 / protobuf 3.0.0 • 現在と比較すると結構違いがある • クライアント更新のハンドリングが難しい • もう少し機能性が枯れた方が止さそう • 強制アップデートがしやすいならGood 58
  59. 3 Protocols with grpc-gateway • gRPC以外のプロトコルもサポート • gRPCクライアントが不安定 • デバッグ用など

    • 3つのプロトコル(エンコーディング) • application/grpc • application/json • application/protobuf • grpc-gatewayを利用 • エンドポイントとメッセージの単純なマッピング 59
  60. 3 Protocols with grpc-gateway • エンドポイントはサービス名とメソッド名 • ボディはメッセージをそのまま • application/jsonならJSONでポスト

    • application/protobufならprotobufのままポスト service EchoService { rpc Echo(EchoMessage) returns (EchoMessage) { option (google.api.http) = { post: “/EchoService/Echo” body: “*” }; } } サービス名と メソッド名 POSTで * 固定 60
  61. 3 Protocols with grpc-gateway • いずれ必要無くなるかも • クライアントの安定化 • PHPもGAに(protobuf

    v3.3.0) • lyft/envoy によるgRPCプロキシ • gRPC ↔ JSON transcoder 61
  62. TCP Load Balancer • NginxによるTCPロードバランサー • PROXY protocol • upstreamにリモートIPを伝えるため

    • 永続接続なのでコネクションが切れない • バックエンド側でgraceful shutdown 62
  63. Forward Proxy • フォワードプロキシによる通信の最適化 • 日本とUS間での接続が遅い • Apache+PHPではリクエスト毎に接続 • gRPCのフォワードプロキシ

    • PHPとプロキシ間はInsecure接続 • プロキシとUSのサーバ間は永続接続 • レイテンシが600msec→150msec PHP Client Proxy gRPC Server gRPC Server Load Balancer JP US 63
  64. カスタムリゾルバと接続の多重化 • ProxyとLB間を普通に繋ぐと1コネクションのみ • 独自のリゾルバでLBへの接続を多重化 • Per RPCでのRound Robin •

    一定間隔で接続のリフレッシュ Proxy gRPC Server Load Balancer gRPC Server Proxy gRPC Server Load Balancer gRPC Server 多重化 64
  65. Protoファイルの管理 • Single Source of Truth • protoは1つのリポジトリで管理 • 言語別の生成物は個別のリポジトリで管理

    • プラットフォームの全ての機能がわかる • 言語毎のクライアントは1リポジトリをpullするだけ • CIによる開発版、最新版の自動生成 • protoをpushすると言語別リポジトリに反映 65
  66. Protoファイルの管理:ワークフロー 1. Protoリポジトリでfeatureブランチ&PR作成 • 各言語リポジトリでfeatureブランチが自動作成 2. Protoの更新をpush • 各言語リポジトリでfeatureブランチが自動更新 3.

    featureブランチを使ってサービス開発 4. ProtoリポジトリのPRをマージ • 各言語リポジトリでmasterを自動更新 5. Protoリポジトリでリリースタグ • 各言語リポジトリでリリースタグ 6. サービスのリリース 66
  67. まとめ

  68. gRPC Bad Parts • HTTP/2 + Trailer • HTTP/2の周辺状況は思ったより悪い •

    クラウドではgRPCが使えない状況は多い • 汎用的なクライアント • curlのようなベストがない • Polyglotが若干使いにくい • ドキュメンテーション • RESTではベストはないが知見はある 68
  69. gRPC Good Parts • HTTP/2 • 高機能・高性能なRPC • クライアントとサーバの自動生成 •

    拡張性 • コミュニティ 69