Slide 1

Slide 1 text

grpc-webによる内製ゲームサーバ基 盤TakashoのUnity WebGL対応 大竹悠人 ゲーム事業本部開発事業部開発二部テクノロジー推進グループ 株式会社ディー・エヌ・エー

Slide 2

Slide 2 text

TL;DR; ● Unity WebGL案件でgrpcベースの内製ゲームサーバ基盤Takashoを使うために ● ブラウザ向けのgrpc派生技術であるgrpc-webをTakasho SDK内で独自に実装し ● 最小の工数で要求を満たす形でUnity WebGL案件に対応させた 2

Slide 3

Slide 3 text

自己紹介 ● 大竹 悠人(Haruto Otake) ○ ゲーム事業本部開発事業部開発二部テクノロジー推進グループ ○ a.k.a @Trapezoid ● 2013年よりDeNAにジョイン ○ Unityに関連した技術サポートと様々な内製ライブラリの実装・保守に従事 ○ 内製ゲームサーバ基盤TakashoのクライアントSDK担当も兼務 3

Slide 4

Slide 4 text

DeNAの 内製ゲームサーバ基盤 Takashoの概要 4

Slide 5

Slide 5 text

内製ゲームサーバ基盤 Takashoとは? ● DeNA内製のゲームサーバ基盤(フレームワーク) ○ 最近のDeNAのゲームの多くで利用している、共通基盤 ○ golang製のゲームサーバと、C#によるクライアントSDK ○ Protocol Buffers IDLを元にサーバ/クライアント双方のコードを自動生成する ● 技術スタック ○ サーバ: golang, k8s(GKE), Cloud Spanner ○ クライアント: C#(非Unity環境も対応) ○ プロトコル: gRPC ■ ゲームクライアントからのTakashoサーバのAPI呼び出しのワイヤプロトコルとして利用 ■ 独自暗号化やSSL PinningをgRPCを使いつつ独自に実装 ● gRPC以外の独自のコード生成も行う ● 参考リンク(既存の講演内容) ○ 社内ゲームサーバー基盤 Takasho とは ○ DeNA の次世代ゲームを支える Takasho と Google Cloud の活用 ○ サーバー"コード"レス!? クライアント中心のモバイルゲーム開発手法 5

Slide 6

Slide 6 text

TakashoクライアントSDKの自動生成 ● gRPC自体が自動生成するコードの他に、Takashoとしての通信の為に必要なコードを自動生成 ○ SDKの利用者が直接gRPCのAPIを呼び出す必要がないように、gRPCを隠蔽 ○ gRPCのエコシステムを活用しつつも、ゲームサーバ基盤としてインターフェースを定められる ■ 暗号化,SSL Pinning,共通ヘッダ,認証,エラーの分類,リトライなどの処理をSDK側で行う 6

Slide 7

Slide 7 text

Takasho SDKによるAPI呼び出しの例 7 API呼び出し用の拡張メソッドを 呼び出してawait リクエストオブジェクトを生成 (Protobufのオブジェクト) エラーハンドリングの為にレスポン スは一層Wrapされている

Slide 8

Slide 8 text

突然でてきた要望 UnityのWebGLビルドから Takashoを利用したゲームサーバに 接続したい 8

Slide 9

Slide 9 text

Unity WebGLビルドとは ● C#で書かれたUnityのゲームをWebブラウザ上で動かす技術 ○ Unityのプレイヤービルド対象に出来るプラットフォームのひとつ ● C#コードは最終的にWebAssemblyに変換されてブラウザ上で動作する ○ 3段階の変換/コンパイルが行われる魔法の技術 ■ C#からMSIL (C#コンパイラ) ■ MSILからC++ (IL2CPP) ■ C++から(LLVMを挟んで)WebAssembly (Emscripten) ○ 描画はWebGLを用いてCanvasに対して行われる 9

Slide 10

Slide 10 text

Webブラウザ上ではgRPCを利用することはできない ● WebブラウザはgRPCサーバに接続するクライアント実装を行うのに必要なAPIを持っていない ○ ブラウザ上から使えるHTTP通信のAPIの抽象度ではgRPCのハンドリングを行うことは不可能 ■ HTTP/2のストリームの単位で操作ができるAPIがなければgRPCは実装できない ○ ソケット通信も行えないので、HTTPスタックから実装し直すことも不可能 10 WebGLビルドから gRPCを使った既存の通信手法で Takashoサーバに接続することは 不可能

Slide 11

Slide 11 text

Webブラウザからゲームサーバに接続する為の選択肢 1. Takashoを使わず、新規にゲームサーバを構築する ○ Swagger等の既存の仕組みでWebGLビルドでも利用できるクライアントSDKを生成できる ○ 既存タイトルで積み上げたゲームサーバ実装を活かせなくなり、実装工数が大きくなる ○ k8s/IaC等の運用上のノウハウも散逸してしまう ○ 不採用。Takashoのまま接続可能にする選択肢を探ることに 2. TakashoにgRPCではない通信手段を実装して、置き換え可能にする ○ TakashoのままgRPCを使わず、通常のHTTPで呼び出せるエンドポイントを定義する ○ サーバ/クライアント共にのフレームワークに大きな手入れが必要になる ■ 1.ほどではないが実装工数が大きい ○ 不採用 3. grpc-webを介して、grpcのTakashoサーバを間接的に呼び出せるようにする ○ 採用 11

Slide 12

Slide 12 text

grpc-webによる 内製ゲームサーバ基盤 Takashoの Unity WebGL対応 12

Slide 13

Slide 13 text

grpc-web ● gRPCをWebブラウザから利用するための派生プロトコル ● gRPC(の一部機能に限って)HTTP/1.1とブラウザのHTTP通信APIで実装可能な仕様にしたもの ○ Unary(1Req:1Res)RPCに対応 ○ ServerStream(1Req:NRes)RPCに対応 ○ ClientStream(NReq:1Res), Bi-DirectionalStream(NReq:NRes)には非対応 ○ Takashoは全てUnaryを採用しているため問題ない ● 既存のgRPCサーバへのリバースプロキシとしてgrpc-webサーバを別途設置するのが主流の構成 ○ フレームワークとしてのTakashoサーバにはほぼ手を入れずに、 クライアントSDKとインフラ面の対応のみの最小工数でWebGL対応を実現できそう... 13

Slide 14

Slide 14 text

grpc-webをクライアントSDKで対応するには 14 Unity WebGLビルドによる 技術的制約をクリアした状態で gRPCによる通信スタックを grpc-webによる通信スタック に置き換える

Slide 15

Slide 15 text

Unity WebGLビルドのgrpc-webの動作の障壁となる制約 ● シングルスレッドでのみ動作し、スレッドを作成できない ○ サブスレッドが動作する前提のライブラリは利用できない ○ Taskを同期的にWaitするなど、非同期処理のやりかた次第では容易にデッドロックする ● ネットワークアクセスに大きな制約がかかる ○ そもそもブラウザ上でソケット通信は利用できないため、System.Net.Socketsは利用不可 ○ System.Net.HttpWebRequestによるHTTP通信も利用不可 ○ UnityのAPIであるWWW/UnityWebRequestでのHTTP通信は利用可能 ● ネイティブプラグインの動作と利用可能な形態に大きな制約がかかる ○ WASM Object Files/LLVM Bitcode/C/C++のソースのみ利用可能 ■ x86/arm等の特定のCPUアーキテクチャ向けにコンパイルされたものは利用できない ○ ネイティブプラグインからのネットワークアクセスが出来ない ■ socket/bindなどのソケット通信用の標準関数がそもそも動作しなくなる ○ JavaScriptのソースを埋め込むことでJavaScriptで実現可能な動作はプラグイン化できる ■ WWW/UnityWebRequestは内部がJavaScript側で実装されている為に通信が可能 ● 消費メモリ量を絞る必要がある ○ 特にモバイルブラウザでは、利用できるメモリ量が端末によっては非常にシビア ■ モバイルWebView上で実行されるケースもあり、その場合は更にシビア ○ ファイルシステムを使えないため、多くのアセットをインメモリに展開する 15

Slide 16

Slide 16 text

Unity WebGL向けのgrpc-web通信スタックの要件 ● メインスレッドのみでも動作する ● WebGLで利用可能な、WWW/UnityWebRequest/JavaScriptプラグインによるHTTP通信を行う ● できるだけ小さなコードサイズ/メモリで動作する 16

Slide 17

Slide 17 text

Unityにおけるgrpc-webの通信スタックの選択肢 1. Grpc.Net.Client.Web ○ gRPCから公式に提供されている、.NET向けのgrpc-webクライアント実装 ○ 公式gRPC実装(Grpc.Net/Grpc.Core)の通信スタック部分を差替可能な構造を利用した実装 ○ HttpWebRequestを用いて実装されているため、Unity WebGLでは動作しないため不採用 2. GrpcWebUnity ○ Transitional Forms社が公開しているOSS ○ Grpc.Net.Client.Webと同様に、Grpc.Netの通信スタックを差し替えた実装 ○ WebGL向けにJavaScriptのfetch APIを利用しており、Unity WebGLで動作する ■ JavaScriptプラグインはEditorでは動作しないため、ビルド後とEditorで実装が異なる ○ Transitional Forms社の他のOSSに依存していて、コード量と依存が多いため不採用 3. grpc-webの通信スタックの独自実装 ○ Grpc.Net/Coreに依存せずUnity WebGLで動作するものを1から実装する ○ 採用 17

Slide 18

Slide 18 text

grpc-web実装をなぜ一から実装したのか ● grpc-webの仕様は非常にシンプルで、grpcとの差分はあまり大きくなく、簡単に実装できる ○ grpc自体も本来は非常にシンプルで、実際のHTTP通信以外で必要な実装量は少ない ○ Takashoで使うUnaryによる通信を実現できればよく、必要な実装量はさらに少なくなる ○ 実際かかった工数も1~2週程度 ● TakashoクライアントSDKはgRPCを隠蔽している為、Grpc.Netをベースにする必要がない ○ Takasho SDKで抽象化がなされているので、Grpc公式実装による抽象化を必要としていない ○ Grpc公式実装なしでも、これまでのgRPC版と同じように使えるインターフェースを実現できる ○ Unity WebGLで重要なコードサイズに考慮したシンプルな実装を提供できるようになる ○ Editorとランタイムでの実装を統一できる ■ JavaScriptによる実装はEditorで動作しない ■ UnityWebRequestを利用することで解決 18

Slide 19

Slide 19 text

grpc-webのフレーム構造 ● PROTOCOL-WEB.mdという文書に必要な情報が収まる程度のシンプルな仕様 ○ HTTP/2でのgRPCのFrame構造を、HTTP/1.1のペイロードに収められるように独自に符号化 ○ HeaderはHTTP Headerとして扱い、Frame構造には含めない ○ Frame構造はTrailer(メッセージ末尾につけるヘッダ)とメッセージ本文となるDataで構成 ■ TLV(Tag-Length-Value)的なシンプルな構造で符号化 ■ TrailerのBodyはHTTP Headerと同等のフォーマットで記述 19 Takashoでは独自暗号化を行うので、 フレーム単位での圧縮は効果が無い。 よって、利用も実装もしない

Slide 20

Slide 20 text

Content-Typeによる通信モード分岐 ● Dataの値はContent-Typeの値によって、テキストとバイナリどちらかの形式になる ○ ブラウザのAPIの挙動による制約の為、利用できる機能範囲に差異がある ● テキスト形式(application/grpc-web-text) ○ DataにProtocol BuffersのバイナリをBase64でテキストにエンコードした上で乗せる ○ UnaryとServer Side Streamingが利用できる ● バイナリ形式(application/grpc-web+proto) ○ Dataに直接Protocol Buffersバイナリを乗せる ○ Unaryのみ利用できる ● TakashoクライアントSDKでは、バイナリ形式のみをサポートする形で実装 ○ TakashoはUnaryのみしか利用しないため、テキスト形式を利用するメリットが無い ○ 暗号化で独自のエンコードもかけるため、Base64エンコードの負荷は避けておきたい 20

Slide 21

Slide 21 text

エラーハンドリング ● gRPCのエラーコードはHTTPのステータスコードとは異なる ○ grpc-webのJavaScript実装を参考に、HTTPステータスコードからの変換を行う ○ grpc-statusヘッダがあるときはその値をエラーコードとする ● grpc-webとしての通信ができなくても、不定な通信エラーとして扱えるようにする ○ ペイロードのFrameデコードに失敗する場合は、テキスト化を試みてエラー文にする 21

Slide 22

Slide 22 text

コードサイズの削減と使い勝手の両立 ● コードサイズ削減の工夫 ○ 公式のgRPC実装(Grpc)自体への依存は完全に排除する ○ grpc-webの中でもTakashoで使う必要のない機能は実装しない ● コードサイズ削減は重要だが、それによってSDK利用者の使い勝手が損なわれてはならない ○ 特に非同期インターフェースの実装手法はコードサイズと使い勝手の双方に大きく影響する 22

Slide 23

Slide 23 text

Awaitableパターンによる非同期対応 23 ● asyncを使うと内部的にステートマシンがコード生成されてしまう ため、コードサイズが増加する ○ Callbackによるインターフェースがコードサイズ面では優秀だが、 使い勝手が悪すぎる ● Task以外でも、Awaitableパターンを満たす型はasyncメソッド内か らawaitキーワードで非同期に終了を待つことが出来る ○ SDKの内部的にはCallbackベースで実装しつつ、呼び出し側では awaitで待機できる ○ 呼び出すメソッド側ではasyncで定義する必要がある ● Awaitableパターンを独自に実装することで、(SDK自体で) asyncを使わずコードサイズの増加を抑えつつ、awaitに対応する

Slide 24

Slide 24 text

AwaitableとCoroutineの両対応 ● Awaitableだけでなく、IEnumeratorも実装することでUnityの Coroutineからも待機可能にする ○ yield returnで待機可能 ● asyncと同様にクライアントSDK内部ではyieldも使わない ○ yieldもステートマシンが生成されてしまうため ● 利用側が非同期処理の手法を自由に選択できるようになる ○ 1. 同期コンテキストの無効化した上でTaskを利用 ○ 2. UniTaskを導入 ○ 3. async/awaitを使わずにCoroutineを利用 ○ 4. Callbackを利用 ○ 5. 同期的にStatusをポーリング 24

Slide 25

Slide 25 text

grpc-webを独自実装するにあたっての作業進行の工夫 ● 新規にプロトコルを利用する場合、サーバ/クライアント両方が手探りな状態での開発は非効率 ○ 問題があった場合に、どちらの実装に問題があるかの切り分けが難しくなってしまう ● grpcwebproxyを用いて、既存のサーバを手元でgrpc-web化してクライアントSDKを先行開発 ○ 手元で単純なEcho gRPCサーバをgrpc-web化し、 (暗号化等なしで)接続可能なクライアントSDKを実装 ○ 次に手元で既存のTakashoサーバをgrpc-web化し、接続可能なクライアントSDKを実装 ○ クライアントSDKの完成後、実際の開発環境のTakashoサーバを正規にgrpc-web化して確認 ● 既存の動作する実装に対して接続可能な実装/構成を徐々に用意していくアプローチ ○ 確度の高い動作確認が可能になる ■ クライアントSDKの対応時には既存ソフトウェアを活かして段階的に確認できる ■ サーバ側の対応時にはすでに接続確認に使えるクライアントSDKが存在する ○ それぞれの作業をできるだけ独立して、高速に進めることができる ■ 最後の段階まで、サーバ担当の工数は不要 ■ サーバ側の構成時もコミュニケーションコストが非常に低い 25

Slide 26

Slide 26 text

サーバでのgrpc-web対応 ● 既存のgRPC接続時のサーバ側の構成 ○ Cloud Load Balancingを通して、GKE上のgolang製のアプリケーションサーバに接続 ○ GKEのPod内では直接要求を受けとらず、envoyをリバースプロキシとして介する 26

Slide 27

Slide 27 text

サーバでのgrpc-web対応 ● envoyのhttp_filterを使い、envoyをgrpc-webのリバースプロキシサーバとして機能させる ○ Takashoサーバ本体のコードは変更せず、envoyの設定変更のみで対応できる ○ envoy.extensions.filters.http.grpc_web.v3.GrpcWeb ● WebGLビルドの場合、CORSへの対応もほぼ必須なので、これもenvoyのhttp_filterで処理 ○ envoy.extensions.filters.http.cors.v3.Cors 27 grpc-web向けの http_filterを追加

Slide 28

Slide 28 text

まとめ ● Unity WebGL案件でgrpcベースの内製ゲームサーバ基盤Takashoを使うために ● ブラウザ向けのgrpc派生技術であるgrpc-webをTakasho SDK内で独自に実装し ● 最小の工数で要求を満たす形でUnity WebGL案件に対応させた 28

Slide 29

Slide 29 text

注記 ● This slide is not affiliated with or otherwise sponsored by CNCF. 29

Slide 30

Slide 30 text

30 ご清聴ありがとうございました End