リアルタイム
• コンピューターの文脈では「すぐにやること」でも「早く処理すること」でもなく、「ある処理に対して一定時間内に必ず結果を返すこと」
• オンラインゲームの世界では、1フレーム(1/60, 16.7ms)で同期する(様に見せる)こと
同期
Player1
Server
Player2
F1 F2 F3 F4 F5
同期 同期
• 一定の間隔で通信内容を同期・演算し、同じ世界を表示
※ フレーム同期、サーバ中継型の場合表示表示
表示 表示
モバイル環境 ※ OpenSignal ネットワーク統計データより
レイテンシ DL Speed UP Speed 信頼度
4G
DOCOMO 52 ms 5 Mb/s 1.3 Mb/s 86%
KDDI 40 ms 7.5 Mb/s 1.7 Mb/s 86%
SoftBank 35 ms 11.3 Mb/s 1.8 Mb/s 86%
3G
DOCOMO 616 ms 1.1 Mb/s 0.2 Mb/s 84%
KDDI 259 ms 0.8 Mb/s 0.1 Mb/s 88%
SoftBank 576 ms 1.4 Mb/s 0.3 Mb/s 83%
参照元: http://opensignal.com/networks/日本
モバイル環境• バラバラな環境の中で、1/60秒(16ms)で動作しているゲームループを遅延することなく、うまくアクションが同期するように見せる
• 1アクションのデータサイズは多くても数十~百Byteちょっとなので、そこまで大きな問題にはならない。レイテンシが辛い
同期項目(一部)同期項目 処理
ゲーム開始 全てのユーザが接続したので、ゲームを開始
時刻 チート対策のため、時刻とレイテンシのチェック
操作キャラ座標位置 ユーザーの位置とボスの位置を同期
モーション ユーザーの操作演出(ジャンプ、回避、攻撃等)
ダメージ ユーザー、敵の受けたダメージを反映
ポイント 取得したオーブの数を同期
アイテム取得 アイテムの取得を同期
アイテム使用 アイテムの使用を同期
ゲームオーバー ユーザーが画面からいなくなる
コンティニュー ユーザーが復帰する
ゲーム終了 結果画面を表示
位置同期システム
• プレイヤーがラグを感じるポイントはどこかというと、「キャラの座標位置の同期」
• シンプルに座標位置を通信してそのまま処理すると、他のプレイヤーが常にワープしながら動いている様な見え方になる
位置同期システム• PSO2: 歩き始めた時に結果をある程度決めてしまう
(※ CEDEC2010 セガの節政さんの公演より)
• メザマシフェスティバル: ジャンプ中に微妙な移動ができるため、結果が1通信で決まらない
• ワープさせずに位置を合わせるためには、位置同期の仕組みをうまく考える必要がある
同期システム
• つまり、0.3秒 + 必ずかかるレイテンシの時間だけ前の世界をシュミレートしている
• PvPは実現できないが、フェスバトルの内容であれば、うまく全員がリアルタイムにアクションしているように見える
チート対策• ゲームデザインとして、ボスは最後に倒した人が2人いたら、どちらも恩恵を受けられるようにした
• 10秒遅延していると、10秒間ボスを殴れる。遅延しているユーザーが有利になる!
• レイテンシの閾値を越えるとエラーにする + 時刻を途中で書き換えらえれたら検知する仕組みを実装
Load Balancer
• EC2のtagをもとに、各クラスタ(lobby/game server)の各ノードごとのコネクション数を毎秒取得、RedisのSortedSetで保管/更新
• APIサーバーからリアルタイムで最も接続数の少ないノードを読み出し、クライアントにエンドポイントを返す
Autoscaling Server• 設定した閾値に応じてサーバーを自動で追加/縮小
• 追加は最短で3分に一度、縮小は1時間に一度のスパンに制限
• 縮小は深夜帯のみトリガー
• 追加より縮小の方がリスクを伴うためシビアに設定
Autoscaling Server
• ゼロダウンタイム
• Socket.ioプロセスが立ち上がり接続が確立できるまでLB側で有効なノードとして認識しない
• 縮小は、ノードに対するユーザーからの接続がない状態でしかトリガーされない
LBとAutoscaling Serverの可用性• Multi-AZ && 自動FailOver
• Lobbyクラスタ用LB、Gameクラスタ用LBの2台構成、別AZに配備
• それぞれがお互いをSocket.ioプロセスベースで監視し合い、障害発生時に一方がもう一方の機能を吸収する形で自動FailOverを行う
• サーバーが復旧した際は自動FailBack
LB実装の経緯
• Socket.ioとELBの相性が悪い
• ELBのTCP Listenerを使った場合、Socket.ioのコネクション確立に必要なStickySessionがサポートされておらず、handshakingが確立できない
LB実装の経緯
• 対処の一つとしては、ELBの下にnginxやHAProxyを立ててip-hashでバランスすることでstickinessを担保する → しかし、ELBの下にさらにnginx立てて・・は辛い。
https://medium.com/@Philmod/load-balancing-websockets-on-ec2-1da94584a5e9
LB実装の経緯• Proxy運用コスト…。
• そもそもnginxだとupstreamの動的変更が出来ない(※)ため、ぶら下がるec2インスタンスの変更に手動対応が必要 → オートスケール実現不可 ※ 実際は有償版のnginxPlus購入で可能なようだが、結局追加モジュー
ルを書く必要がある。
→ 今回の実装方法に至る
実装した結果• LBが直接ユーザーからのコネクションを受けることがないため、障害点になるリスクが低い
• 本質的な部分(※)の実装は薄く、モジュール化可能 ※ 各ノードの任意の状態(コネクション数やCPU使用率など)を常時監視し、設定した閾値に応じて任意のスクリプトをトリガー
• 今回のsocket.ioのようなニッチなケースに限らず、http以外のプロトコルでAutoscalingさせたい時等に使える
• SPDY/HTTP2やwebRTC等
まだまだ改善したいことがあります
• パフォーマンス
• 特にマルチプレイ中のCPU使用率が高すぎる。コツコツとチューニングしていく必要がある
• 操作性
• 「片手持ち」の条件下でできる限りのことはやってみたが、お客様はもっと良いものを求めている