Slide 1

Slide 1 text

GoConference’19 gRPC Streaming による スケーラブルな常時接続型 API の構築 Yutaka Imoto DeNA

Slide 2

Slide 2 text

GoConference’19 今日のお題: gRPC Streaming ● サービス紹介 ● 直面したスケーラビリティの課題 ● 解決策 ● gRPC Streamingの紹介 ● インフラ構成 2

Slide 3

Slide 3 text

GoConference’19 弊社のタクシー配車サービス MOV 3

Slide 4

Slide 4 text

GoConference’19 4

Slide 5

Slide 5 text

GoConference’19 5 - スマホアプリでタクシーに配車依頼 - 電話をかけなくてOK - 全車輌ネット決済対応 - 事前のクレカ登録で、車内での支払い無し - 対応エリア現在拡大中 - 東京、神奈川、大阪、京都

Slide 6

Slide 6 text

GoConference’19 アプリ画面 ● ユーザーの現在地周辺の、配車可能な タクシーが表示される ● 3秒に1度、タクシーの位置が更新される ○ 動きはアニメーションで補完される 6 注:開発中のダミー画面です 実際の車輌の位置情報は含みません

Slide 7

Slide 7 text

GoConference’19 パフォーマンスの課題 7

Slide 8

Slide 8 text

GoConference’19 8 アーキテクチャ

Slide 9

Slide 9 text

GoConference’19 9 パフォーマンスに 課題 - DB に負荷のかかる処理 重い!!!

Slide 10

Slide 10 text

GoConference’19 既存アーキテクチャの課題 - DB に負荷の高い処理 - ユーザーの近傍車輌の取得 - 営業圏ポリゴン上にあるかどうかの判定 - 配車禁止エリアポリゴン上にあるかどうかの判定 - この処理がユーザー数に比例して発生 - ユーザーアプリは3秒に一度 API を叩く - DBはスケールしにくいので、システム全体のスケーラビリティに課題 - 突発的なアクセス増加に耐えられない - DBのインスタンス代が高い 10

Slide 11

Slide 11 text

GoConference’19 解決策 11

Slide 12

Slide 12 text

GoConference’19 MQTT 3秒に1度 publish 位置情報、タクシーの状態等 - 無駄な計算を抑制したい - 車輌の情報が更新されたタイミングで、ポリゴン上にあるかどうかの判定したい - ユーザー数に応じて、オートスケールしたい - 車輌台数に応じて、オートスケールしたい - 車輌台数も一定の規模ある。対応地域拡大で一気に増える - 車輌搭載のAndroid 端末から、タクシーの位置やステータス ( e.g. 空車かどうか )が、3秒に一度送 られてきている 新アーキテクチャでやりたいこと 12 車輌情報配信システム

Slide 13

Slide 13 text

GoConference’19 新アーキテクチャ方針 - アプリ→API間 - アプリはAPI のポーリングはやめる - アプリはAPI と常時接続する Push 型 API とする - アプリは配車可能な車輌のみを API から Push され受けとる - 車輌情報配信システム→API間 - 必要最小限の回数のみ、ポリゴン判定したい - こちらも常時接続型API とし、車輌情報が送られてきたら、配車可能なもののみ配信する - ユーザ側と車輌側でスケールの観点が異なるので、APIのプロセスを分け、それぞ れ別にスケールさせたい 13

Slide 14

Slide 14 text

GoConference’19 新アーキテクチャ 概要 - 新システムは、gRPC Streaming とRedis PubSubを活用する - ユーザーに、ユーザー近辺の車輌情報を配信する Subscribe Server - 車輌情報を受け取り、メッシュ判定等をし、配車可能なものを配信する Publish Server - 地図を1km四方に細分化し(3次メッシュ)、その領域中の車輌情報を配信する Redis PubSub の channelを用意する 14 車輌情報配信システム gRPC Streaming gRPC Streaming Publish Subscribe

Slide 15

Slide 15 text

GoConference’19 Redis Pubsub - Redis は インメモリ KVS として有名だが、Pub/Sub 機能もある - メッセージのqueueing はしない。Publishする時点で、Subscribeしている クライアントにだけ伝達され る。 - 今回は Redis を Pub/Sub 機能のみ利用。データの永続化はしない。 15 source: https://aws.amazon.com/jp/pub-sub-messaging/

Slide 16

Slide 16 text

GoConference’19 新アーキテクチャ 全体像 16 車 輌 情 報 配 信 シ ス テ ム Cloud Load Balancing Publish Server Kubernetes Subscribe Server Kubernetes Cloud Load Balancing gRPC Publish Server Kubernetes Publish Server Kubernetes Subscribe Server Kubernetes Subscribe Server Kubernetes gRPC gRPC gRPC Publish Server 車輌台数に応じた オートスケール Redis Cluster シャード負荷に 応じてスケール Subscribe Server ユーザーに応じた オートスケール ユーザーアプリ

Slide 17

Slide 17 text

GoConference’19 新アーキテクチャ 全体像 17 車 輌 情 報 配 信 シ ス テ ム Cloud Load Balancing Publish Server Kubernetes Subscribe Server Kubernetes Cloud Load Balancing gRPC Publish Server Kubernetes Publish Server Kubernetes Subscribe Server Kubernetes Subscribe Server Kubernetes gRPC gRPC gRPC Publish Server 車輌台数に応じた オートスケール Redis Cluster シャード負荷に 応じてスケール Subscribe Server ユーザーに応じた オートスケール ユーザーアプリ - 必要な箇所が必要なだけスケール! - DB がボトルネックとなることを回避できた! - コスト削減!

Slide 18

Slide 18 text

GoConference’19 gRPC Streaming について 18

Slide 19

Slide 19 text

GoConference’19 gRPC とは - Google 製、多言語/多 Platform の RPC フレームワーク - クライアントは別のマシンのサーバーのメソッドをローカルオブジェクトのように直接 呼び出す - シリアライザにProtocol Buffers - コードジェネレーター - 通信は HTTP/2 経由で行う 19 19 source: https://grpc.io/docs/guides/

Slide 20

Slide 20 text

GoConference’19 gRPC の通信方式 - 4分類 - Unary RPC - Server streaming RPC - Client streaming RPC - Bidirectional streaming RPC 20

Slide 21

Slide 21 text

GoConference’19 gRPC の通信方式 - Unary RPC - Unary = 単項 - Request : Response = (1, 1) 21 source: https://qiita.com/yuzo777/items/046910c95559cf0fff68

Slide 22

Slide 22 text

GoConference’19 gRPC の通信方式 - Server streaming RPC - サーバーは1 Request に対し、複数の Response を一連の応答として返す - サーバーは、すべての応答を送り返した後、サーバー側のステータス等を返して完了 - クライアントは、すべてのサーバーの応答を受信したら完了。タイムアウトも設定可 22 source: https://qiita.com/yuzo777/items/046910c95559cf0fff68

Slide 23

Slide 23 text

GoConference’19 gRPC の通信方式 - Client streaming RPC - 今度は、クライアントが単一のリクエストではなく、複数の一連のリクエストをサーバーに送信する - サーバーは単一の応答を返す - サーバーはすべてのリクエストを受け取る前に、レスポンスを返しても良い 23 source: https://qiita.com/yuzo777/items/046910c95559cf0fff68

Slide 24

Slide 24 text

GoConference’19 gRPC の通信方式 - Bidirectional streaming RPC - リクエストとレスポンスがそれぞれ複数 - リクエストもレスポンスのタイミングは独立している。同期してもしなくてもよい 24 source: https://qiita.com/yuzo777/items/046910c95559cf0fff68

Slide 25

Slide 25 text

GoConference’19 今回用いた gRPC の通信方式 25 車輌情報配信システム gRPC Streaming gRPC Streaming Publish Subscribe Client streaming gRPC Server streaming gRPC

Slide 26

Slide 26 text

GoConference’19 gRPC Hello World 26

Slide 27

Slide 27 text

GoConference’19 gRPC Hello World - Unary RPCのものをみてみましょう - proto ファイルの定義 - proto ファイルから Go のコードを生成 - gRPC サーバー・クライアントの実装 - Server Streaming RPC の場合 27

Slide 28

Slide 28 text

GoConference’19 proto の定義 28 source: https://grpc.io/docs/quickstart/go/

Slide 29

Slide 29 text

GoConference’19 proto ファイルから Go のコードを生成 29 source: https://grpc.io/docs/quickstart/go/

Slide 30

Slide 30 text

GoConference’19 source: https://grpc.io/docs/quickstart/go/ proto ファイルから Go のコードを生成 - request/response 用の struct 30

Slide 31

Slide 31 text

GoConference’19 proto ファイルから Go のコードを生成 - server用の interface - client 用のinterface 31 source: https://grpc.io/docs/quickstart/go/

Slide 32

Slide 32 text

GoConference’19 Server Streaming RPC の場合 - Server interface - Client interface - Recv() が (nil, io.EOF) で サーバー側完了 32 source: https://grpc.io/docs/reference/go/generated-code/

Slide 33

Slide 33 text

GoConference’19 今回のサーバーの実装について 33

Slide 34

Slide 34 text

GoConference’19 Subscribe Server - ユーザーアプリが接続する API - あるエリアの配車可能な車輌情報を 3秒に一度返す - Server Streaming RPC として実装 - 今回はレスポンスに終わりはないので、サーバーに明示的に終了を告げる口を用意 している - 34

Slide 35

Slide 35 text

GoConference’19 Publish Server - 車輌情報が打ち上げられるAPI - Client Streaming RPC として実装 - 35

Slide 36

Slide 36 text

GoConference’19 Go で良かった点 - 簡単にサーバーのCPU リソースを限界まで使うことが出来た - よしなにサーバーへの接続数を増やしても、 内部で goroutine が適切にスケジューリングされる - ワーカー数の調整など、特にチューニング作業することなく、限界まで CPUリソースを使うことができ た - これにより、簡単にサーバー 1台あたりのユーザー数を増やすことができた - 今回のように CPU ヘビーな API であり、 CPU 使用率でオートスケールする構成に はありがたい 36

Slide 37

Slide 37 text

GoConference’19 インフラ構成 37

Slide 38

Slide 38 text

GoConference’19 インフラ構成 - 方針 - なるべくマネージドなクラウドサービスを使う - 負荷に応じたオートスケール - APIサーバー - API サーバーのバックエンドには、 GCP の Kubernetes Engine, GKE を採用 - CPU 使用率に応じ、HPA でオートスケール - Redis サーバー - GKE 上に Redis Cluster を自前で構築・運用 - 本当は マネージド・サービスである Google Cloud Pub/Sub を用いたかったが、レイテンシーの要 件を満たせず断念 38

Slide 39

Slide 39 text

GoConference’19 ロードバランサーについて - 課題 - オートスケールしても負荷の偏りが持続する問題 - gRPC Streaming に対応する クラウドの LB はどれだ問題 39

Slide 40

Slide 40 text

GoConference’19 LB について / 負荷の偏りが持続する問題 40 Cloud Load Balancing Subscribe Server Kubernetes Subscribe Server Kubernetes オートスケールしても 既存の接続はそのまま持続し 新しい Pod に向き直したりしない Subscribe Server Kubernetes Subscribe Server Kubernetes Subscribe Server Kubernetes Subscribe Server Kubernetes Cloud Load Balancing オートスケール! サーバーとユーザー間の接続が持続している

Slide 41

Slide 41 text

GoConference’19 - 課題 - オートスケールしても既存 Pod の負荷が下がらない - 接続が持続しているため、既存ユーザの接続は新しい Pod に向かない - 対策 - 長期間接続が発生する部分では、定期的に切断 &再接続処理を行う - 今回 - Subscribe Server → アプリの仕様として、長いコネクションは発生しない想定 - Publish Server → 定期的に切断&再接続 - Redis → 定期的に切断&再接続 LB について / 負荷の偏りが持続する問題 41

Slide 42

Slide 42 text

GoConference’19 gRPC Streaming に対応する LB - GKE - Ingress - BETA - ReadinessProbe が httpGet でのみ動作する。gRPC 層でヘルスチェックできない 。 httpGet での実装・検証事例はある [1] - LoadBalancer Service - BETA ではない [2] - `protocol: "TCP"` で、L4LBとして動かす - Ingress とは違い、SSL 終端は自前で行う必要がある - 今回はこちらを採用 - ReadinessProbe で exec を利用し、gRPC 層でヘルスチェックできる 42 [1]GKE gRPC Ingress LoadBalancing https://medium.com/google-cloud/gke-grpc-ingress-loadbalancing-4b9cdbc09758 [2]https://cloud.google.com/kubernetes-engine/docs/concepts/service

Slide 43

Slide 43 text

GoConference’19 まとめ 43

Slide 44

Slide 44 text

GoConference’19 - gRPC Streaming + Go で スケーラブルな常時接続型 API を作った事例紹介を しました - 弊社のタクシー配車アプリにて、ポーリング型からPush型の常時接続APIに変更す ることで、スケール可能かつ低コスト化を達成した - gRPC Streaming をクラウドサービスで実現するためには、対応状況を見極める必 要がある - 特に LB 周り まとめ 44