Slide 1

Slide 1 text

リアルタイム対戦 × バックエンドアーキテクチャ RYO TOMARU

Slide 2

Slide 2 text

自己紹介 職歴 ゲーム系企業でクライアント /サーバーを主軸に しつつ、配信サービスのフロントエンド実装など も経験。 趣味 個人ゲーム開発、ドット絵、最近引退気味です が光の戦士。最近はGolangとFlutterが好き。 戸丸 良 / RYO TOMARU

Slide 3

Slide 3 text

本日の内容 ● どのような技術を用いて「リアルタイム対戦」を実現したか ● 通信フローについてのご紹介 ● 苦労したポイント ※詳細なインフラ構成については今回の範囲外となります ...。

Slide 4

Slide 4 text

目次 1. リアルタイム通信をどの技術で実現するか 2. 今回採用した技術 3. 苦労したポイント 4. まとめ

Slide 5

Slide 5 text

リアルタイム通信をど の技術で実現するか

Slide 6

Slide 6 text

多彩な選択肢 最近では、ゲームエンジン側でもサポートが進むなど、開発がしやすい環境となってきてい る。 ● 純粋なSocketを用いて構築する ● Unity+MLAPI/Mirror/DOTSNET … ● Unity + Photon/モノビットエンジン/MagicOnion ● gRPC Bidirectional Streaming RPC ● WebSocketやDatagram … etc …

Slide 7

Slide 7 text

選択肢が多い上に、一つ一つが難しい

Slide 8

Slide 8 text

多彩な選択肢 「リアルタイム通信対戦がしたい」だけであれば、どの手段でも実現は可能。 ここに「サービス・ゲームデザインとしてはどうか 」「運用保守の面でどうか」「UXとしてどう か」など、様々な要素を加えて選択していく必要がある。 (難しい・・・) 今回は担当プロダクトにおいて、どのような選択をしたのか、苦労話と合わせてご紹介しま す。

Slide 9

Slide 9 text

今回採用した技術

Slide 10

Slide 10 text

gRPCを利用 ● 他の機能(API)はgRPCで実装していたため、同一の運用で対応したかった ● Unary RPC ○ Request/Response ○ 一般的なAPI ● Client Streaming RPC ○ Clientから任意にデータ送信が可能 ○ ファイルアプロードなど ● Server Streaming RPC ○ Serverから任意にデータ送信可能 ○ 通知など ● Bidirectional Streaming RPC ○ 双方向通信 ○ 相互に任意のタイミングでデータ送信が可能

Slide 11

Slide 11 text

初期に採用したのは・・・ ● 初期(モック)時点では、ゲームデザインが完全に FIXしていなかった ● 単純に双方向を選択しておけば大抵なんとかなるよね、から選択 ● 慣れていた gRPC Bidirectional Streaming RPC

Slide 12

Slide 12 text

gRPC Bidirectional Streaming ● 俗に言う双方向通信 ● クライアント、サーバー相互に任意のタイミングでデータ送信が行える ● oneofを使用し、一つのstreamで複数のコマンドを定義 message Notify { // どれか1つの情報がくるので、 switchなどで処理を振り分ける oneof event { ChatInfo chat_info = 1; Actor join_actor = 2; … } } service StreamingService { rpc StartNotify(...) returns (stream Notify) {} … }

Slide 13

Slide 13 text

順調に実装は進んでいったが・・・

Slide 14

Slide 14 text

実装自体は進んだが、次第に様々な問題が発生 複雑化するコード Client/Server間のやり取りの増加 操作に対して必ず結果が必要 テストの複雑化 増えるoneof 開発コスト増加 send/recvそれぞれのgoroutineを 管理する必要 このメソッドってクライアントに何か投 げるんだっけ・・・? 増え続けるoneof 増え続けるoneof 増え続けるoneof

Slide 15

Slide 15 text

pickup 増え続けるoneof ● Request/ResponseをC/Sのmessageへ追加する ● 関係性が非常に難解 message ClientNotify { oneof event { Say say = 1; Foo foo = 2; Bar bar = 3; Echo echo = 4; … } message Say {...} … } message ServerNotify { oneof event { SayResult say_result = 1; FooResult foo_result = 2; BarResult bar_result = 3; EchoResult echo_result = 4; … } message SayResult {...} … }

Slide 16

Slide 16 text

まだ間に合う・・・はず! (そもそもゲームデザイン的にあっていないのでは? )

Slide 17

Slide 17 text

最終的な着地点 ● gRPC + Unary RPC + Server Streaming RPC ● 各種操作については、Unary RPC(Request/Response)へ変更 ● 自身の操作などはServer Streaming RPCを利用して通知 ● より詳細な情報が必要であれば Unary RPCで取得 gRPC + Unary RPC + Server Streaming RPC 意外とすんなりいけるはず

Slide 18

Slide 18 text

最終的な着地点 ● gRPC + Unary RPC + Server Streaming RPC ● 各種操作については、Unary RPC(Request/Response)へ変更 ● 自身の操作などはServer Streaming RPCを利用して通知 ● より詳細な情報が必要であれば Unary RPCで取得 gRPC + Unary RPC + Server Streaming RPC 全部書き直した

Slide 19

Slide 19 text

gRPC + Unary RPC + Server Streaming RPC

Slide 20

Slide 20 text

Redis PubSubを利用

Slide 21

Slide 21 text

結果としてどうなったか ● 基本的にUnary RPCを利用するため、フローがシンプルになった ○ いつも通りの実装、テスト手法が通用した ● Request/ReponseがIDLにて明示的に定義されるため、読みやすい ○ この操作きたら何を返すんだっけ・・・が無くなった ● Unaryは操作、Streamingはサーバーからの通知と役割を切り分けられた ○ CRUDとNotifyを切り離せたので、可読性が上がった

Slide 22

Slide 22 text

苦労したポイント

Slide 23

Slide 23 text

苦労したポイント 再接続時の復帰処理 復帰時の進行状況に応じて状態を再現する必要があった。 また切断中は自動行動を行うため、タイミングによっては進行不能になるなど、 リリース直前まで頭を悩ませられた。 細かいエッジケース対応 〜の時に〜をするとバグる、という様な話が初期は特に多かった。 最終的にはBOTを作成し、自動的にバトルを繰り返す仕組みでカバーした。 ローンチ後、大きな問題は起きていないので、良い結果が出せた。 参考: https://qiita.com/maruc/items/2393647be5caa32623e3

Slide 24

Slide 24 text

苦労したポイント PubSubに悩まされた go-redisを使用していたが、コネクション数が多大になったり、 ConnectionPoolとPubSubの接続状態連携が行われていないなど ...。 最終的にはある程度自前でコントロールを行った。 負荷対策 システム上、最新のデータを取得するケースが多く、 Primaryアクセスが頻発。 キャッシュアサイドパターンを用いることで、負荷軽減を行った。 (整合性を担保する必要があるので、可能であれば避けたい所ではあった)

Slide 25

Slide 25 text

pickup go-redis pubsub ● 1購読=1コネクションが貼られる ● アクティブが1万人いたら1万コネクション ○ パフォーマンスが著しく低下 ○ ElasctiCacheの上限にもかかる ● 最終的にAPでコネクションを自前プールするよう実装 ● 一つのコネクションで複数サブスクライブできるように変更 ● 内部的に各Sessionのgo:channelへpush ○ ch <- event

Slide 26

Slide 26 text

まとめ

Slide 27

Slide 27 text

まとめ ● 当たり前ですが「ゲームデザインにあった」技術選定、設計が重要。 ● 表面上動くものはすぐに作れる時代ではあるが、その先が深い。 ○ このタイミングで切断されたら・・・ ○ 地下鉄で瞬断したら・・・ ○ バイナリが改造されたら・・・ etc ● 「どのようにテストを行うか」も初期段階で考えておくと良い。 ● 開発中はクライアント担当とずっと「うーん・・・」と唸ることになる。 ● ・・・とはいえ、開発自体は楽しい!

Slide 28

Slide 28 text

ご静聴ありがとうございました