１億DAUの中華ゲームを支えるアーキテクチャ

## 前書き

[「AWS & Game Advent Calendar 2020」](https://qiita.com/advent-calendar/2020/aws-game)二日目の記事になります。

近年日本では、アズールレーン、荒野行動、原神など中国制のゲームが多く進出されています。
最近では、[「原神１ヶ月間251億円を稼いだ」](https://i.gamebiz.jp/?p=281105)や[「王者栄耀のDAUが1億を超えた」](https://jp.techcrunch.com/2020/11/03/2020-11-01-tencent-claims-record-100m-daily-users-on-mobile-game-honor-of-kings/)などニュースにもなっています。

私は中国出身のため、この1年の仕事中でも、関連の内容が聞かれたことは何回かありました。今回のAdvent Calendarをきっかけに、[王者栄耀](https://ja.wikipedia.org/wiki/%E7%8E%8B%E8%80%85%E6%A0%84%E8%80%80)や[荒野行動](https://ja.wikipedia.org/wiki/%E8%8D%92%E9%87%8E%E8%A1%8C%E5%8B%95)をはじめ、典型的な中華ゲームのアーキテクチャと日本のゲームの違いについて、説明したいと思います。

最後、AWS&Gameのカレンダーですが、ゲーム自体はAWSで動いていることとイコールになりません。実際この記事で触れたゲームの中、荒野行動のグローバルとアーチャー伝説だけがAWSの公開事例になります。また、この投稿は、個人の意見であり、所属する企業や団体は関係ありません。データは全部ネット上で集められたものです。

## サーバ選択のシーンを見たことがありますか？

日本のモバイルゲームではあまり見かけない光景ですが、中国のゲームはサーバ選択式なものがそれなりにあります（中国本土のゲームは更に多いです）。例えば、[アズレン](https://gamewith.jp/azurlane/article/show/72955) や [ライコス](https://altema.jp/saintseiyarc/serverer)など。
![スクリーンショット 2020-12-01 22.01.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163447/d5b61067-460b-15ef-bf60-71db31d530da.png)
日本のモバイルゲームはブラウザゲームから発展したものが多く、LAMPベースのステートレスなアーキテクチャが多いです （図1）。一方中国のモバイルゲームはネットゲーム(MMO)から発展したものが多いため、MMOライクなステートフルなアーキテクチャが多いです（図2)。つまり、ユーザが接続するサーバが決まっているため、サーバ選択が必要になります。
![p1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163447/d20f49bc-25f1-0124-cd56-0491eafc8ea0.png)

もちろんゲームの仕様により、ステートフルじゃないと実現できないゲームがあるとと思いますが、どちらかというと、ステートフルとステートレスで両方ともできる場面なら、日本ゲームはステートレスの構成になりがちですが、中国は逆のパターン、ステートフルのケースが多いです。

## ステートフルなゲームサーバの特徴

ステートフルの構成を採用した理由について、歴史的な原因はありますが、ソリューションアーキテクトの観点から見て、図２にはいくつかLAMP構成より優れているところもあります。

1. **構成がシンプルで、拡張しやすい**。LAMP構成は、データベースがボトルネックになりやすいです。特にユーザが大量に来るゲームでは、DBをシャーディングしないといけなくて、その作業を予めやらないと後が大変になります。またユーザの事前見積もりかなり重要であり、10万DAUベースで負荷試験を行い、100万DAUが来ましたら、リソースを増やすだけで解決できないケースが多く、shard数の修正、キャッシュの追加及びコードの修正も必要な場合はよく見られます。一方、図２の構成では、事前に１セット（ゲームサーバ+DB)あたり捌けるユーザ数を決めて、ユーザが多く来たら、同じようなセットを横に並べば大丈夫のため、簡単に増やせますし、DBのシャーディングも必要ありません。
2. **単体サーバのパフォーマンスが良い**。ゲームクライエントとサーバ間はSocket通信のため、httpのようなで都度接続のオーバーヘッドが無いです。また、構成がシンプルなため、負荷試験によりサーバ最大限のパフォーマンスを引き出せる可能性もあります。

アーキテクチャの観点以外、サーバサイドの作り方にも違いがあります。個人感覚になりますが、日本はDBにユーザの状態を管理することが多いことに対して、中国系のゲームはユーザの状態をサーバ側で管理し、DBはバックアップの役割を担うことが多いです。その理由について、中国で流行っているゲームフレームワーク[skynet](https://github.com/cloudwu/skynet/wiki/GettingStarted)からの記述を引用して解説致します。

>簡単なwebアプリケーションなら、事前にデータフォーマットを定義し、リクエストを受けるたびに、DBからユーザの情報を読み取り、処理後DBに書き込みすることが一般的です。ネットゲームにおいて、強いコンテキストを持ち、ユーザ間の連携も多いです。もし同じモードを採用すると、DBの処理とロジックの処理の間、ボトルネックになりやすい、このボトルネックネックは通常キャッシュをレイヤーを追加するだけで解決できないことが多いです。

 >Skynetでゲームホストする場合は、同期的にデータをDBに保存することはおすすめしません、代わりにインメモリのDBに保存したほうが良い。サービス、アプリロジックやゲームデータは全部メモリで常駐します。もしDBはあなたのフレームワークの一部でしたら、殆どの場合バックアップとして使うべき。データの状態が変わったもしくは定期的にDBに保存します。アプリは直接メモリからデータを呼びます。  

 <details><summary>オリジンの英語文書</summary><div>  
 For simple web applications, it's more common to put user-related status or data (pre-defined data structure) in a database, after getting the client's request, the server application loads the user's data, process it and write it back to the database. For online game applications, there will be more context and interaction between data and services. If using the same model for these two types of applications, it's easy to have a bottleneck in database and feature modules, which cannot be resolved even by adding a layer of memory cache.
With Skynet as your game server, it's not recommended to synchronize your service data to the database, it's best to put it as a data structure in memory. Service, application logic code and game data are kept in the local memory. If a database is already a part of the game server framework, in most cases, it's used as a backup of game data. You can push your data into a database when state changes, or backup your data periodically. The application uses game data from memory directly.
 </div></details>  

このように、ユーザの状態を全部サーバ側で管理し、DBは定期的バックアップだけのため、リクエスト来るたびにDBにアクセスしなくなり、パフォーマンスも良くなります。特に中国製のモバイルゲームでは、ネットゲームに近い形なものが多く、複雑なロジックやユーザ連携も多いため、リクエストが来るたびにDBにアクセスすると、パフォーマンスどころか、ゲーム自体も成立しないこともありえます。なので、ゲームは自然的にこのような作りになりました。

余談ですが、ステートフルの構成が多いため、サーバサイドの評価指標として、１サーバあたりにどのぐらいユーザを捌けることはよく見られています。最初に紹介した１億のDAUのMOBAゲーム王者栄耀では、多くのゲームの中で優れたものであり、PVPサーバ単体だけで1.2万人のユーザを捌け、PVPでは無い部分は２万人を捌けることができます。

## ステートフルなゲームサーバの課題

一方、もちろん全部良い話ではありません。ステートフルな構成では以下の課題があります。

1. 可用性はLAMP構成より低い、DDoS攻撃にされやすい
ユーザはサーバに直接繋ぐため、そのサーバがダウンしたらユーザは一時的にプレイ不能になりますし、データロストの可能性もあります。また、ロードバランサーがなく、個々サーバのIPがpublicになっているため、攻撃されやすいです。

2. サーバ跨いたユーザ連携はしにくい
本質はサーバシャドーイングので、DBのシャドーイングと似たような悩みがありました。

3. コンテナやスポットインスタンスとの相性が悪い
ステートフルのため、コンテナにおける開発環境標準化のメリットやスポットインスタンスにおける90%コスト削減の恩恵を受けにくいです。

## 進化し続けるアーキテクチャ
上記デメリットがあるため、最近ゲームでは更に進化し、下記のような三層構成になっています。実際王者栄耀や荒野行動もそのアーキテクチャの拡張の感じになります。![p2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163447/d489af79-5bce-65e8-29d8-7aeab225e1f2.png)

1. 単一サーバからマイクロサービスに変換
ロジックに密に連携必要なところは同じところに固まりながら、ソーシャル、プレイステータスなど分割できそうなところは別のサービスとして分割します。こうして、分割された部分は高い可用性の構成も検討できますし、コストもサービスの特性にい応じて更に最適化することが可能です。荒野行動を作ったNetEeseさんは、チャットやProxyなどネットワークヘビーなワークロードにARMインスタンスを導入し、50%近くのコスト削減ができました。今年、第２世代のGraviton2のインスタンスもGAになり、さらなるコスト削減が期待できるかもしれません。

2. Gateway及びproxyサーバの導入
個々サーバのIPを公開するのではなく、通信は全部単一IpのGateway経由で行うことにより、サーバが攻撃されたリスクを減らせます。個別通信が必要の場合も、クライエントから決めるではなくgatewayに問い合わせして、繋がるサーバを決め決めます。AWSサービスの中では、Amazon gameliftも似たような考え方です。事前に複数サーバを用意し、ユーザがゲームを加入するときだけ、Gameliftに問い合わせし、適切なサーバを案内します。そして、サーバが攻撃されリソースが足りなくなったら、自動スケールの仕組みで新しいサーバを起動し、ユーザを守ります。興味ある方は、こちらの[記事](https://aws.amazon.com/jp/blogs/gametech/protect-multiplayer-game-servers-from-ddos-attacks-using-amazon-gamelift-2/) を参考していただければと思います。proxyサーバもDDoS攻撃から保護することができます。また異なるリクエストを適切なサーバに転送する役割もなっており、例えば、大型マップ間のシームレス移動とか、バトルしながらギルドチャットにメッセージを送るとか。

![p3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163447/7e6e8dd8-111c-a6e8-f36f-ffde3d21983c.png)

これでステートフルサーバの課題が多く解決されて、"理想"的なアーキテクチャとも言えますが、コンポーネントが多く、構成が複雑であり、運用コストが高いと思います。荒野行動や王者栄耀などDAUが膨大、ロジックが複雑なゲームでは採用を検討できるかもしれませんが、規模が小さいゲームやシンプルなカジュアルゲームはそこではやる必要はないと思います。近年、[アーチャー伝説](https://www.facebook.com/archerogame/) を開発したHabbyを始め、一部の中国制ゲームは、極力運用コストを抑えたフルサーバレスのアーキテクチャの採用も見られます。

[Habbyのサーバレスゲームアーキテクチャ](https://aws.amazon.com/cn/solutions/case-studies/habby/)
![p4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163447/2db302cd-f02c-f75e-bffa-27827b116994.png)
![](https://d1.awsstatic.com/architecture-diagrams/customers/China/habby-arch-diag-1.34b5d1a4450c8973dcf300d6ae98c332d522cb6e.png)

## おまけ：１億DAUの王者栄耀で使うサーバ技術は？

先程アーキテクチャの話をしましたが、サーバ技術についてもう少し触れます。王者栄耀はMOBAゲームの中、少し特殊で、CS方（サーバ集中処理型）ではなく格ゲーでよく使われているキー入力同期の技術を利用しています。CS側は全部サーバで計算するため、チートがしにくいというメリットがありますが、プレイ感触が少し悪いです。事前予測や細かい調整を用いて、プレイヤー体験を最適化はできますが、開発コストがかかるため、キー入力同期を採用しました。サーバはUDPプロトコルを使って、送られてたフレームをまとめてクライエントに返します。毎回３フレーム冗長分を送信し、ネットワークが悪い時更に多めに送信しています。描画が60fpsまでできますが、操作は66ms単位で秒間15フレームで行っています。
キー入力同期は一番の課題は同期ズレであり、float利用を回避、ランダムシードの対策を入れても、バグや機種による同期ズレははやり発生します。自動化テスト、専用のテストサーバを用意や録画を全部アップロードなど施策で、継続改善した結果リリース時は2%ぐらいですが、その後色々改善した結果、2018年時点では約0.03%ぐらいになりました。

## 最後

ここまで読んでいただいてありがとうございます。もし少しでもご参考になりましたら、ぜひLGTMやSNSでシェアをしていただければと思います。
この記事で利用した一部のデータと画像は以下の参考資料から取得したものになります。

https://d1.awsstatic.com/events/reinvent/2019/REPEAT_Build_cost-effective_web_&_containerized_apps_on_EC2_Arm_instances_CMP329-R.pdf

https://mp.weixin.qq.com/s?__biz=MzUzOTc2MjcwOQ==&mid=2247484253&idx=1&sn=bc947f52b7309a8fe17422a7d2d63f0e&chksm=fac2ca27cdb54331070657646f38d4f49fe6fe55bf55def43187903b65aff8378661202b988e&token=362991571&lang=zh_CN#rd

https://gameinstitute.qq.com/community/detail/115396
https://gameinstitute.qq.com/community/detail/118047

https://aws.amazon.com/cn/solutions/case-studies/habby/