Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ステートフルで大規模アクセスのあるsoft-realtimeなゲームサーバーをeasyにつくる

 ステートフルで大規模アクセスのあるsoft-realtimeなゲームサーバーをeasyにつくる

#elixirfest #elixirfestjp

4b071f90c5d9c0a58e2d9076460b7be4?s=128

さっちゃん

June 16, 2018
Tweet

More Decks by さっちゃん

Other Decks in Programming

Transcript

  1. ステートフルで 大規模アクセスのある soft-realtimeな ゲームサーバーを easyにつくる

  2. .。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆)

  3. None
  4. .。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆) 株式会社ドリコム ソフトウェアエンジニア (サーバーサイド)

  5. 本日は知見を共有しに来ました。 皆さんが同じ苦労を味ははない為にrailを敷いてゆきたい。

  6. AWSのKubernetes上で、WebSocketを使ふElixir serverを運用するまで。

  7. HTML5 (ほぼWebGL) のスマホ向け2Dゲーム TCG。Real time PvP

  8. Raugh system architecture REST API WebSocket REST API PubSub PubSub

    一部metrics収集 Matching 対戦
  9. 開発teamを立ち上げる

  10. 開発teamを立ち上げる 前提 : 資産が無い。 • 新規開発である。 • Real time対戦serverの知見があまり無い。 •

    HTML5 game applicationのserver側の知見が無い。 Elixirの運用経験が在る。(Elixirを使ってSidekiqを操作する https://qiita.com/ohr486/items9/db88866786ee8bb89d9) Prototype開発期間であり、失敗したら作り直せる。
  11. どのやうに開発を始めたか アプリを作る前にrailを敷く心。 • Credo • Dialyzer • mix test •

    eye_drops • docker-compose.yml • PULL_REQUEST_TAMPLATE.md • CODE_OF_CONDUCT.md
  12. どのやうに開発を始めたか アプリを作る前にrailを敷く心。 • Credo • Dialyzer • mix test •

    eye_drops • docker-compose.yml • PULL_REQUEST_TAMPLATE.md • PULL_REQUEST_TAMPLATE.md • CODE_OF_CONDUCT.md https://hex.pm/packages/inner_cotton
  13. どのやうに開発を始めたか アプリを作る前にrailを敷く心。 • CI/CD (Digdag) • ChatOps (Hubot) Teamが小さい &

    アプリが小さい内にやる。
  14. どのやうにElixirを学んだか (私個人) 前提 : Elixirの開発 & 運用経験は在る。 Erlang processを使って設計した開発は無い。つまりErlang/Elixirは素人。 関数型言語は好き。趣味ではHaskellを使ふ。

  15. どのやうにElixirを学んだか (私個人) Elixirの3大特徴 : • 関数型言語 • GenServer (状態としてのprocess) •

    Supervisor (監視tree)
  16. どのやうにElixirを学んだか (私個人) 関数型言語 : 知ってた。好き。 HaskellとClojureをやってゐる。好き。

  17. どのやうにElixirを学んだか (私個人) GenServer (状態としてのprocess) : 飛行機本にはいっぱい書いてあるがあれはオレオレGenServerの作り方であって使ひ方 ではない。Frameworkは使へなければいけない。 GenServerを使ふlibraryを作って学んだ。 (https://hex.pm/packages/holiday_jp) Parallel計算の不具合は登場人物2人で大体は再現する。1人ではなく2人で考へる。

  18. どのやうにElixirを学んだか (私個人) Supervisor (監視tree) : ApplicationとGenServerを使ってゐたらだいたい使へる。 色々脳内simulationして作ってゐる。もっといい設計方法を作りたい。 Hot code reloadを実践利用してゐないのでその辺り曖昧…。

  19. どのやうにElixirを学んだか (私個人) Q. 本読んだ? A. 読んでない。(後になってから読んだ。) 一番読んでるのはElixirとErlang/OTPの公式document。 • https://elixir-lang.org/docs.html •

    http://erlang.org/doc/
  20. どのやうにElixirを学んだか (team) 実は何もやってない。 皆が学んだ事を常時会話してゐる。(わりと結構常時。) 余計な事を喋っても、余計な事をやっても、責めない。

  21. Real time対戦の設計

  22. Real time対戦の設計 Matching用のchannelと対戦用のchannelを分けた。 Matchingには色々なlogicが在る為、複数種の matching channelを使ひ分けてゐる。

  23. Matchingの設計 独立したmicro serviceをElixirで作った。 近いratingのUserが来るまで待ちつつ、rating幅を広げてゆく。 1台のserverで、Redisのsorted setに対して無限loopしてゐるだけ。 対戦serverとはPhoenix.PubSubで通信する。

  24. 対戦のprocess設計 Userを特定のserverにroutingしない。 serverの前でroutingする代りに、後ろでPubSubする。 何も考へずにserverを増減させられる。 LBはALB (coreos/alb-ingress-controller)。

  25. 対戦のprocess設計 1つの対戦を管轄する唯一のprocessは存在 せず、channel毎に計算する。 DBにlockを取得して排他制御する。 (Distributed locks with Redis https://redis.io/topics/distlock) ChannelとRedisだけなので構成が簡単に

    できたが、複数のchannelで1つの対戦を管 理する故に時間経過管理が複雑になってし まった。
  26. Serverを終了する Deployに関はるところ。 K8sのPreStopで、接続してゐるchannelが 無くなるまで待つ。

  27. 対戦のlogic設計 logic計算は、対戦状態を入力し、対戦状態と計算履歴を出力する純粋関数。

  28. 対戦のlogic設計 Elm (Redux) architectureを基に設計した。 優先度付きqueueのちゃんとしたlibraryが無かったので、 作った。(https://hex.pm/packages/pqueue2)

  29. Master dataの管理 https://hex.pm/packages/mnemonics • 読み取り専用。 • on-memoryで高速。 • 再起動せずに新しいver.のdataへ入れ 替へられる。

    • 古いver.で処理してゐた計算はそのまま 古いver.を読み出し続けられる。 • Heap領域にcacheできる。読み出した dataをsnapshotとしてsystem外に持 ち運べる。 • Parallel。
  30. Elixir runs on Kubernetes

  31. Elixir run on Docker Local環境ではDocker Composeで開発してゐる。 Erlang/OTPとElixirの両方のver.を固定したいので、base imageを作った。 https://hub.docker.com/r/nesachirou/elixir/

  32. Build for release Distilleryでmix releaseする。 Docker multi stage buildでimageを軽くする。 Umbrellaでmicro

    serviceを開発し、Distilleryで別々にbuildしてゐる。 Stagingと本番で全く同じimageを使ふ (可搬性) 為に、config.exsではなく環境変数 で設定する。(https://hexdocs.pm/distillery/runtime-configuration.html) 環境変数はK8sのConfigMapから設定する。
  33. Phoenixの起動を待つ K8sのReadiness probeで、HTTPを受け付けられるようになるまで待つ。 https://hex.pm/packages/komachi_heartbeat

  34. Kubernetes on AWS まずAWSである事が前提。

  35. Kubernetes on AWS K8sを選んだのは : • Game logicが大量に載ってゐる & PvPなので、hot

    deployは考へたくなかっ た。 • Elixirの標準のdeploy & scale方法が無かったので、containerに入れて何も 考へたくなかった。 • Dockerに含まれる等、de facto standardである。 • GCPでもAzureでも使へる。(当時はAWSでのみ使へなかった。) • 将来の為の知見としても有用。
  36. Kubernetes on AWS 当時EKSは無かった。のでkopsでclusterを作ってゐる。 社内標準のCentOS with itamaeをkopsで使へるやうに改造していただき、インフラ teamにclusterを運用してもらってゐる。 みなさんは迷はずEKSを使ってください。

  37. Deploy to Kubernetes 全てをcodeに記載する。 itamaeでDigdagを構築する。 Digdagでimageをbuildし、kubectl set imageする。 cluster設定はchartとして書き、Helmで適用する。

  38. 監視 監視は社内標準のZabbix。 Node (インスタンス) のmetricsは社内標準のmackerel。 APMはAppSignal + ReconEx。error通知はSentryで行ふのでerror_loggerは 外す。 error通知は社内標準のSentry。logger_sentryを使ってゐたが追加情報を送れな

    いのでやめた。Sentry付属のerror_logger + 自作のErrorLogger macroを丁寧 に埋め込んである。 Logは、DaemonSetでfluentdを立て、CloudWatch + S3に送る標準的なやり方 をしてゐる。
  39. Performance

  40. 最適化 実は最適化はしてゐない。 最適化より、安定し、scaleさせる事に力を割いてきた為。 ERLANG IN ANGERに書いてある事くらいは注意する。

  41. 負荷試験 GatlingからWebSocketで繋いで対戦する。 Gatling実行インスタンスを複数用意して負荷を増やす。

  42. 負荷試験 問題 : Logger。 Stagingでdebug logを見たい & stagingと本番で同じDocker imageを使ひたい →

    Loggerのlevelだけ設定し、compile_time_purge_levelを設定してゐなかっ た。 Loggerに大量のmessageが詰まり、常にsync_thresholdを超える。全ての処理が IO待ちになる。 Stagingでのdebug logを諦め、compile_time_purge_levelを設定した。 Loggerの各種thresholdを1桁くらい上げた。
  43. 負荷試験 問題 : 対戦dataがmemoryを使ひ切る。 対戦dataをRedisに一時保存してあり、計算時に取り出す。取り出したところで memory不足でprocessがcrash (process毎にmemory制限をかけてある)。 Erlangの一部の外部表現は大きくなる。特にNEW_FUN_EXTとか (External Term

    Format http://erlang.org/doc/apps/erts/erl_ext_dist.html)。 Master dataと共通するdata (Mnemonics.Snap) を保存前に削除し、master dataから毎回取り直す。 `:erlang.term_to_binary(&1,[:compressed])`で圧縮する。
  44. 負荷試験 問題 : Master dataがmemoryを使ひ切る。 前頁でmaster dataのETSから頻繁にdataをcopyする事になり、GCが追い付かず memory不足でprocessがcrash。 頻繁にcopyするmaster dataを計算の初めにMnemonics.Snapに載せる。(そし

    てRedisに保存する前に空にする。)
  45. 負荷試験 問題 : Redixに処理が詰まる Redix Connectionのmessage queueが詰まる。 そら、そう(〃l _ l)

    Connection poolを実装した。後にRedisZとなる (後述)。
  46. 負荷試験 問題 : Redis (KVS) へのnetworkを使ひ切る。 当時PubSubもKVSもnetworkがbottleneckになってゐた。 `:erlang.term_to_binary(&1,[:compressed])`で圧縮する (PubSubでも工夫すれ ばできる)。

    Redisをshardingする (後述)。
  47. 負荷試験 問題 : Redis (PubSub) へのnetworkを使ひ切る。 そもそもpublishを減らす。Publish先を抽象化するstructを作り、Channel topic ではなくstructに向けてpublishする。Publishが不要だと判定したら、send/2で済ま す。

    `:erlang.term_to_binary(&1,[:compressed])`で圧縮する。但しmapしかpublish できないのでvalueだけ圧縮する等dirty hack。 Dataを直接にpublishせずKVSに置き、keyだけをpublishする。 Redisをshardingする (後述)。
  48. 負荷試験 問題 : CPUを使ひ切ってくれない。 不明。 K8s側で対処する。Podに割り当てるCPU量を少なくし、Nodeに詰めるPod数を増や す。 Server台数で補ふ。

  49. 負荷試験 問題 : PubSubがscaleしない。 させた (https://hex.pm/packages/phoenix_pubsub_redis_z)。 後でこれについてのLTが在ります。

  50. 負荷試験 問題 : Redixがscaleしない。 させた (https://hex.pm/packages/redis_z)。 後述。

  51. Serverをscale in/outさせる Nodeさえ確保できれば、scale outは簡単。 前述のAppTerminatorに対戦終了を待たせる事で、serverを突然死させずにscale inする。

  52. Redisをscaleさせる PubSubにもDBにもRedisを使ってゐる。 Redisをscale outさせる。 PubSub KVS

  53. Redis (PubSub)をscaleさせる Phoenix.PubSub.Redisを捨て、自作のPubSub adapterに差し替へた (https://hex.pm/packages/phoenix_pubsub_redis_z)。 後でLTが在ります。

  54. Redis (KVS)をscaleさせる Redis clusterは運用しない。 ShardingするRedixのwrapperを作った。

  55. Redis (KVS)をscaleさせる https://hex.pm/packages/redis_z • No downgrade from Redix: pipeline concurrency

    & auto reconnection. (https://hexdocs.pm/redix/real-world-usage.html) • Parallel connection pooling. • Sharding support. • Auto reconnect at Amazon ElastiCache Multi-AZ failover. (https://rubygems.org/gems/redis-elasticache)
  56. Let’s share 知見. Let’s `mix hex.publish`.