$30 off During Our Annual Pro Sale. View Details »

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)

    View Slide

  2. 自己紹介
    • 佐野 正浩(@kazegusuri)
    • Mercari, Inc → Souzoh, Inc (出向)
    • Principal Engineer
    • Platform Team
    • GoとPHPで開発しています
    2

    View Slide

  3. Agenda
    • gRPCの概要
    • gRPCの使い方
    • REST と Browser
    • gRPCの新機能
    • メルカリでの事例
    • まとめ
    3

    View Slide

  4. gRPCの概要

    View Slide

  5. gRPCとは
    • gRPC Remote Procedure Call
    • HTTP/2を使ったRPCフレームワーク
    • 汎用的で高性能
    • 現代的な機能
    • マイクロサービスに適している
    • 2015年2月にGoogleが公開
    • 2016年8月にGA
    • 現在 v1.3
    • Cloud Native Computing Foundationのプロジェクト
    • サービス定義
    • 提供するメソッド
    • リクエスト/レスポンスの構造
    • デフォルトでProtocol Buffersを利用
    5

    View Slide

  6. Protocol Buffers V3
    • 強い型付け
    • 効率的なバイナリデータ形式
    • JSONマッピング
    • IDL(Interface Definition Language)による記述
    • サービス定義
    • RPCシステム(gRPC)向けのインタフェイス定義
    • 複数の言語に対応したコード生成
    • メッセージ型
    • クライアントとサーバの両方
    6

    View Slide

  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

    View Slide

  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

    View Slide

  9. gRPCの特徴
    • 単方向とストリーミングを含む4つのRPC
    • 単一コネクションと通信の多重化
    • RPCの終了
    • キャンセル
    • タイムアウト & デッドライン
    • ステータスコード
    • メタデータ
    • メッセージフォーマット非依存
    9

    View Slide

  10. Unary RPC
    • 送受信が各1回ずつのRPC
    Client Server
    Request
    Response
    10

    View Slide

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

    11

    View Slide

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

    12

    View Slide

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

    13

    View Slide

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

    View Slide

  15. RPCの終了
    • クライアントとサーバは独立してRPCを終了できる
    • 片方が成功、片方が失敗も許容
    • RPCのキャンセル
    • 双方がいつでも即時キャンセル可能
    • デッドラインとタイムアウト
    • クライアントはRPC完了を待つ時間を指定可能
    • サーバ側ではRPCがタイムアウトを確認可能
    15

    View Slide

  16. ステータスコード
    • gRPC独自のステータスコード
    • HTTPステータスコードを直接利用しない
    • ステータスでエラーハンドリングを明確に
    • 例
    • OK
    • InvalidArgument
    • NotFound
    • AlreadyExists
    • Unauthenticated
    • Unavailable
    • DeadlineExceeded
    • Internal
    16

    View Slide

  17. メタデータ
    • RPCに対する付加情報
    • 認証のためのクレデンシャル
    • エラーに対する詳細情報
    • Key/Valueペアのリスト
    • リクエストとレスポンスそれぞれに付与できる
    • HTTPにおけるヘッダーのようなもの
    17

    View Slide

  18. メッセージフォーマット非依存
    • gRPCは通信方式の仕様のみ
    • Protocol Buffersがデフォルトで採用
    • 他のエンコーディングで置き換え可能
    • JSON, FlatBuffers, XML, Thrift…
    18

    View Slide

  19. サポート言語
    • 主要な言語は対応済み
    • 完成度や機能性は言語毎に違う
    • C core base
    • C++, C#, Objective-C
    • Ruby, Python, PHP, NodeJS
    • Swift, Haskell (experimental)
    • grpc-java
    • Java, Android Java
    • grpc-go
    19

    View Slide

  20. gRPCの使い方

    View Slide

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

    View Slide

  22. proto定義を書く
    • EchoServiceを作成
    • Echoメソッドのみを持つ
    • リクエストとレスポンスはEchoMessage型
    syntax = "proto3";
    package echo;
    service EchoService {
    rpc Echo(EchoMessage) returns (EchoMessage);
    }
    message EchoMessage{
    string msg = 1;
    }
    22

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  28. REST と Browser

    View Slide

  29. REST APIは終わった?
    • gRPCとprotobufで簡単に高性能な実装が可能
    • REST APIはもはや必要ない…?
    • まだREST APIが必要な場所は多い
    • 後方互換性のため
    • gRPCサポートが未成熟な言語向け
    • ウェブブラウザ
    • 一度慣れるとRESTに戻りたくない…
    29

    View Slide

  30. grpc-gateway
    • RESTful JSON APIからgRPCへのリバースプロキシ
    • Go実装
    • protobufのサービス定義からコード生成
    • Cloud Endpoints for gRPCのOSS版のようなもの
    https://github.com/grpc-ecosystem/grpc-gatewayより
    30

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  36. Swagger (OpenAPI)
    • RESTful APIの仕様を記述するための規格
    • その周辺ツール
    • Swagger SpecがそのままOpenAPI Specに
    • grpc-gateway用のproto定義から自動生成可能
    $ protoc -I. --swagger_out=. echo.proto
    36

    View Slide

  37. 37

    View Slide

  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

    View Slide

  39. gRPC-Web
    • Improvableが公開
    • その実装をもとにgRPC-Web protocolが出来た
    • 3つのコンポーネント
    • grpcweb
    • gRPCサーバのラッパー
    • ts-protoc-gen
    • TypeScript/JavaScript用のprotocプラグイン
    • grpc-web-client
    • TypeScript/JavaScript用のgRPCライブラリ
    39

    View Slide

  40. grpcweb
    • gRPCサーバをgRPC-Web protocolでラップ
    • CORSプリフライトとprotocol変換
    • Go実装
    • Go以外のgRPCサーバ向けにプロキシもある
    • Trailerをヘッダーやペイロードで表現しなおす
    • 実装量は少ないがやっていることはハック
    40

    View Slide

  41. grpc-web-client
    • ブラウザ向けgRPC-Webクライアント
    • ts-protoc-genで生成されたコードから利用される
    • TypeScript/JavaScript
    • FetchAPIを利用
    • 未対応の場合はXMLHTTPRequestを利用
    41

    View Slide

  42. ステータス
    • アルファ版
    • Unary RPC, Server streaming RPCのみ対応
    • Streams APIが入れば変わるかも
    • 仕様自体もまだ議論中
    42

    View Slide

  43. gRPCの新機能

    View Slide

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

    View Slide

  45. Interceptor
    • WAFでのミドルウェアのような仕組み
    • 以前は仕組み自体がなかったため大変だった
    • RPCのメソッド呼び出し時に共通処理を挟み込める
    • 認証、ログ、メトリクス計測など
    • クライアント側とサーバ側の両方で利用可能
    • 最近はこれを利用したミドルウェアが増えてきた
    45

    View Slide

  46. エラー時の詳細情報
    • gRPCではエラー時のベストプラクティスがなかった
    • ステータスコードとメッセージしか返せない
    • メタデータで返す…?
    • そもそもエラーにせず全部OKで返す
    • メタデータで返すのが推奨とあるがあまり例を見ない
    46

    View Slide

  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

    View Slide

  48. Server Reflection
    • サーバと通信するにはサービス定義が必要
    • クライアントに事前に配布する必要がある
    • Server Reflection
    • サービス定義を返すためのサービス
    • 現状はProtocol Buffersに依存
    • デバッグツールなどにも便利
    48

    View Slide

  49. ロードバランシング
    • 永続接続の負荷分散は難しい
    • 接続した場所にリクエストを続ける
    • クライアントにより偏りがある
    • TCPロードバランサー
    • Protocolに依存しない
    • Per-Connection
    • バックエンドの接続先の振り分けがしづらい
    49

    View Slide

  50. gRPCのロードバランシング
    • Per-Call(RPC) ロードバランシング
    • クライアント側でのロードバランス
    • バランサーを通すか通さないかの2種類のポリシー
    • backend(ラウンドロビン)
    • grpclb
    • 仕組みとしては2段階
    • 名前解決(naming)
    • ロードバランシング
    • round_robin, grpclb
    50

    View Slide

  51. 名前解決(naming)
    • 接続先情報を取得
    • デフォルトがDNS
    • SRVレコードで詳細なデータも返せる
    • APIで独自の実装が可能
    • 接続先情報は3つ
    • アドレス
    • IPアドレス, ポート番号
    • バランサータイプ
    • grpclbかどうか
    • サーバ名
    • (SSLなどの)認証用
    51

    View Slide

  52. ロードバランス(round robin)
    • 名前解決で直接バックエンドサーバを取得
    • クライアント側で分散する
    • 各サーバにコネクションを張りっぱなし
    Name
    Resolver
    gRPC
    Client
    gRPC Server gRPC Server gRPC Server
    52

    View Slide

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

    View Slide

  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

    View Slide

  55. メルカリでの事例

    View Slide

  56. なぜメルカリでgRPCを採用するか
    • 新規マイクロサービス作成コスト
    • 複数のサービス+複数の言語
    • Strongly Typed
    • JSONよりもパフォーマンスが良い
    • Single Source of Truth
    • 最新のサービス定義
    • 全てのサービスの定義をまとめて管理
    • 全てをgRPCで統一している訳ではない
    56

    View Slide

  57. メルカリでの事例: プラットフォーム
    • プラットフォームとマイクロサービス化
    • モノリシックからマイクロサービスへ
    • メルカリ アッテやメルカリ カウルとの連携
    • プラットフォームは全てgRPC+Goで実装
    • 複数言語向けのクライアント提供
    • サービス内のコンポーネント間通信もgRPC
    57

    View Slide

  58. メルカリでの事例: クライアントアプリ
    • iOS/AndroidからgRPCで直接APIと通信にトライ
    • Androidで問題が出て断念
    • 2016年8月頃
    • gRPC 1.0 / protobuf 3.0.0
    • 現在と比較すると結構違いがある
    • クライアント更新のハンドリングが難しい
    • もう少し機能性が枯れた方が止さそう
    • 強制アップデートがしやすいならGood
    58

    View Slide

  59. 3 Protocols with grpc-gateway
    • gRPC以外のプロトコルもサポート
    • gRPCクライアントが不安定
    • デバッグ用など
    • 3つのプロトコル(エンコーディング)
    • application/grpc
    • application/json
    • application/protobuf
    • grpc-gatewayを利用
    • エンドポイントとメッセージの単純なマッピング
    59

    View Slide

  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

    View Slide

  61. 3 Protocols with grpc-gateway
    • いずれ必要無くなるかも
    • クライアントの安定化
    • PHPもGAに(protobuf v3.3.0)
    • lyft/envoy によるgRPCプロキシ
    • gRPC ↔ JSON transcoder
    61

    View Slide

  62. TCP Load Balancer
    • NginxによるTCPロードバランサー
    • PROXY protocol
    • upstreamにリモートIPを伝えるため
    • 永続接続なのでコネクションが切れない
    • バックエンド側でgraceful shutdown
    62

    View Slide

  63. Forward Proxy
    • フォワードプロキシによる通信の最適化
    • 日本とUS間での接続が遅い
    • Apache+PHPではリクエスト毎に接続
    • gRPCのフォワードプロキシ
    • PHPとプロキシ間はInsecure接続
    • プロキシとUSのサーバ間は永続接続
    • レイテンシが600msec→150msec
    PHP
    Client
    Proxy
    gRPC Server
    gRPC Server
    Load
    Balancer
    JP US
    63

    View Slide

  64. カスタムリゾルバと接続の多重化
    • ProxyとLB間を普通に繋ぐと1コネクションのみ
    • 独自のリゾルバでLBへの接続を多重化
    • Per RPCでのRound Robin
    • 一定間隔で接続のリフレッシュ
    Proxy
    gRPC
    Server
    Load
    Balancer
    gRPC
    Server
    Proxy
    gRPC
    Server
    Load
    Balancer
    gRPC
    Server
    多重化
    64

    View Slide

  65. Protoファイルの管理
    • Single Source of Truth
    • protoは1つのリポジトリで管理
    • 言語別の生成物は個別のリポジトリで管理
    • プラットフォームの全ての機能がわかる
    • 言語毎のクライアントは1リポジトリをpullするだけ
    • CIによる開発版、最新版の自動生成
    • protoをpushすると言語別リポジトリに反映
    65

    View Slide

  66. Protoファイルの管理:ワークフロー
    1. Protoリポジトリでfeatureブランチ&PR作成
    • 各言語リポジトリでfeatureブランチが自動作成
    2. Protoの更新をpush
    • 各言語リポジトリでfeatureブランチが自動更新
    3. featureブランチを使ってサービス開発
    4. ProtoリポジトリのPRをマージ
    • 各言語リポジトリでmasterを自動更新
    5. Protoリポジトリでリリースタグ
    • 各言語リポジトリでリリースタグ
    6. サービスのリリース
    66

    View Slide

  67. まとめ

    View Slide

  68. gRPC Bad Parts
    • HTTP/2 + Trailer
    • HTTP/2の周辺状況は思ったより悪い
    • クラウドではgRPCが使えない状況は多い
    • 汎用的なクライアント
    • curlのようなベストがない
    • Polyglotが若干使いにくい
    • ドキュメンテーション
    • RESTではベストはないが知見はある
    68

    View Slide

  69. gRPC Good Parts
    • HTTP/2
    • 高機能・高性能なRPC
    • クライアントとサーバの自動生成
    • 拡張性
    • コミュニティ
    69

    View Slide