Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

gRPC and REST with gRPC in practice

gRPC and REST with gRPC in practice

kazegusuri

June 15, 2017
Tweet

More Decks by kazegusuri

Other Decks in Technology

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とは • gRPC Remote Procedure Call • HTTP/2を使ったRPCフレームワーク • 汎用的で高性能

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

    IDL(Interface Definition Language)による記述 • サービス定義 • RPCシステム(gRPC)向けのインタフェイス定義 • 複数の言語に対応したコード生成 • メッセージ型 • クライアントとサーバの両方 6
  6. 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
  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 { ... } サービス定義 メソッド定義 リクエスト メッセージ レスポンス メッセージ メッセージ 定義 8
  8. gRPCの特徴 • 単方向とストリーミングを含む4つのRPC • 単一コネクションと通信の多重化 • RPCの終了 • キャンセル •

    タイムアウト & デッドライン • ステータスコード • メタデータ • メッセージフォーマット非依存 9
  9. RPCの終了 • クライアントとサーバは独立してRPCを終了できる • 片方が成功、片方が失敗も許容 • RPCのキャンセル • 双方がいつでも即時キャンセル可能 •

    デッドラインとタイムアウト • クライアントはRPC完了を待つ時間を指定可能 • サーバ側ではRPCがタイムアウトを確認可能 15
  10. メタデータ • RPCに対する付加情報 • 認証のためのクレデンシャル • エラーに対する詳細情報 • Key/Valueペアのリスト •

    リクエストとレスポンスそれぞれに付与できる • HTTPにおけるヘッダーのようなもの 17
  11. サポート言語 • 主要な言語は対応済み • 完成度や機能性は言語毎に違う • C core base •

    C++, C#, Objective-C • Ruby, Python, PHP, NodeJS • Swift, Haskell (experimental) • grpc-java • Java, Android Java • grpc-go 19
  12. gRPCの使い方 • サーバをGo、クライアントをRubyで実装 • サーバ実装 1. proto定義を書く 2. protoからコードを生成 3.

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

    package echo; service EchoService { rpc Echo(EchoMessage) returns (EchoMessage); } message EchoMessage{ string msg = 1; } 22
  14. 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
  15. サービスを実装する • 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
  16. 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
  17. 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
  18. クライアント実装 • サーバに接続してメソッドを呼び出すだけ • 実質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
  19. REST APIは終わった? • gRPCとprotobufで簡単に高性能な実装が可能 • REST APIはもはや必要ない…? • まだREST APIが必要な場所は多い

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

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

    } service EchoService { rpc Echo(EchoMessage) returns (EchoMessage) { option (google.api.http) = { post: “/v1/echo” body: “*” }; } } 31
  22. 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
  23. マッピングルール • 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
  24. マッピングルール • パラメータをメッセージにマッピング 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
  25. マッピングルール • パスをメッセージにマッピング 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
  26. Swagger (OpenAPI) • RESTful APIの仕様を記述するための規格 • その周辺ツール • Swagger SpecがそのままOpenAPI

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

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

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

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

    • Trailerをヘッダーやペイロードで表現しなおす • 実装量は少ないがやっていることはハック 40
  31. ステータス • アルファ版 • Unary RPC, Server streaming RPCのみ対応 •

    Streams APIが入れば変わるかも • 仕様自体もまだ議論中 42
  32. Server Reflection • サーバと通信するにはサービス定義が必要 • クライアントに事前に配布する必要がある • Server Reflection •

    サービス定義を返すためのサービス • 現状はProtocol Buffersに依存 • デバッグツールなどにも便利 48
  33. 名前解決(naming) • 接続先情報を取得 • デフォルトがDNS • SRVレコードで詳細なデータも返せる • APIで独自の実装が可能 •

    接続先情報は3つ • アドレス • IPアドレス, ポート番号 • バランサータイプ • grpclbかどうか • サーバ名 • (SSLなどの)認証用 51
  34. 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
  35. なぜメルカリでgRPCを採用するか • 新規マイクロサービス作成コスト • 複数のサービス+複数の言語 • Strongly Typed • JSONよりもパフォーマンスが良い

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

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

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

    • 3つのプロトコル(エンコーディング) • application/grpc • application/json • application/protobuf • grpc-gatewayを利用 • エンドポイントとメッセージの単純なマッピング 59
  39. 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
  40. TCP Load Balancer • NginxによるTCPロードバランサー • PROXY protocol • upstreamにリモートIPを伝えるため

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

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

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

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

    featureブランチを使ってサービス開発 4. ProtoリポジトリのPRをマージ • 各言語リポジトリでmasterを自動更新 5. Protoリポジトリでリリースタグ • 各言語リポジトリでリリースタグ 6. サービスのリリース 66
  45. gRPC Bad Parts • HTTP/2 + Trailer • HTTP/2の周辺状況は思ったより悪い •

    クラウドではgRPCが使えない状況は多い • 汎用的なクライアント • curlのようなベストがない • Polyglotが若干使いにくい • ドキュメンテーション • RESTではベストはないが知見はある 68