Slide 1

Slide 1 text

gRPC and REST with gRPC in practice API Meetup Tech Deep Dive #1: gRPC Masahiro Sano (@kazegusuri)

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

gRPCの概要

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

gRPCの使い方

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

サービスを実装する • 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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

クライアント実装 • サーバに接続してメソッドを呼び出すだけ • 実質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

Slide 28

Slide 28 text

REST と Browser

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

マッピングルールの記述 • optionでメッセージへのマッピングを定義 service EchoService { rpc Echo(EchoMessage) returns (EchoMessage); } service EchoService { rpc Echo(EchoMessage) returns (EchoMessage) { option (google.api.http) = { post: “/v1/echo” body: “*” }; } } 31

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

マッピングルール • 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

Slide 34

Slide 34 text

マッピングルール • パラメータをメッセージにマッピング 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

Slide 35

Slide 35 text

マッピングルール • パスをメッセージにマッピング 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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

37

Slide 38

Slide 38 text

gRPCのブラウザ対応 • 一部のブラウザしか対応していない機能がある • HTTP/2, raw binary, Trailer • 特にTrailer • grpc-gateway • RESTful JSON APIからgRPCへのプロキシ • gRPC-Web • gRPC protocolをブラウザ向けに拡張 • github.com/improbable-eng/grpc-web 38

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

gRPCの新機能

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

Status message • エラーをStatusメッセージで表現することが可能に • detailsがAny型なのでどんな値も格納可能 • 統一した詳細なエラー表現が可能に • 実装としてはメタデータにprotobufをそのまま格納 package google.rpc; message Status { int32 code = 1; string message = 2 repeated google.protobuf.Any details = 3; } 47

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

メルカリでの事例

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

まとめ

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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