質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.59%

Node.jsでゲームサーバーを構築し、サーバープログラム更新時にサーバーを停止せず更新したい

解決済

回答 1

投稿

  • 評価
  • クリップ 1
  • VIEW 2,675

score 25

オンライン対戦ゲームのサーバープログラムをNode.jsで作成しています。
※WebSocketを使用した、1対1の将棋のようなターン制のゲーム
※オンライン接続時にルーム作成。空きルームが存在すればそこに参加。複数のルームでそれぞれ対戦が行われるイメージ。

後々追加したい機能があるため、サーバープログラムもその都度更新する予定です。
その際に、現在対戦中のプレイヤーの処理を中断せずに更新をしたいと考えています。
(もちろん、大幅な修正がある場合は別ですが)

調べたところ、Node.jsはホットデプロイ機能がないためグレースフルリスタートで対応するのが良いとわかりました。
PM2のgracefulReloadを使用すればそのような処理が可能になるのでしょうか。

[参考サイト]
https://qiita.com/ikemura23/items/b3481393d4edca2d5188
https://blog.morugu.com/entry/2018/01/30/222200

npm install -g pm2

pm2 start xxx.js --name appname

pm2 restart appname

また、上記参考サイトに

引用テキスト「pm2 restart:稼働中のプロセスを正常に(処理が終了してから)終了して再読込する.」

とありましたが、例えばサーバープログラムが以下の場合

var ws   = require ('ws').Server;

var wss = new ws ({port: (process.env.PORT||3000)});

var socketList = [];
wss.on ('connection', function (socket) {
    socketList.push(socket);
    socket.on ('message', function (message) {
        console.log(message);
        // 実際にはここでマッチング処理や対戦中の処理を行う。
    });

    socket.on('close', function () {
            socketList= socketList.filter(function (conn, i) {
            return (conn === socket) ? false : true;
        });
        console.log("close");
    });

});


クライアントがサーバーに接続してきたソケットを保存し、切断時にそれを破棄していますが、
pm2 restart appnameを実行時には以下のどのような挙動になるでしょうか。

  1. 全てのクライアントがサーバーから切断しない限りは古いプロセスは終了しない
  2. コールバックが全て処理された時点で再読み込みされる
  3. その他

挙動が2の場合、socketListにまだ要素が残っている時に再読み込みされた場合には
新しいプロセスで同じ変数に情報は引き継がれるのでしょうか。

ご教授お願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

Node.jsはWebSocketのパイオニア的な存在なので、
凄まじいパフォーマンスを出しそうに見えますが、
コヤツは実は1台のマシンで1万の同時接続を捌けません。

1サーバに同時接続出来る人数を5000人とかに絞って、
縦割りで実装すれば今回の質問のような所まで問題を持っていけると思います。

ですが、そもそも別方向で考えれば質問文の疑問も一発解決出来る妙案があります。


フロントで動くWebSocketのAサーバはロジック部分を持たず、
ユーザとの通信のみに集中します。
つまり、本質問で懸念しているロジックを更新するためにプロセスを落としたら接続どうするねん問題から逃げます。

そしてサーバーサイドにもう一台、WebサーバもしくはWebSocketサーバを立ち上げます。
これをBサーバと名付けます。

Bサーバはロジック部分を受け持ちます。
ゲームの進行状況、対戦者の情報、スコア…といった状態も持たせておきます。

Aサーバからの接続を待ち受けて、リクエスト通りにゲームを進行させ、結果をAサーバに返します。
AサーバはBサーバにお伺いを建てながらプレイヤー達に結果を返します。
つまり、Bサーバはフロントからアクセス出来ないようにしつつ、1台サーバを噛ませる事でフロントからの接続を切らせずにバックエンドを更新する事が可能になるでしょう。


この場合、ゲームのアップデートの為にBサーバを落とすとなると、
プレイ中のデータが全て消えてしまいます。

なので極力Bサーバの変数領域やメモリ空間等にゲームのデータを持たせてはいけません。
RedisやMySQL等のデータベースに持たせるような作りにしましょう。

後はAサーバ→Bサーバへお伺いを立てるリクエストが失敗したら、
再度通信するような仕組みを作っておけばBサーバは更新し放題になるでしょう。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/03/04 18:21

    まさに知りたいことドンピシャで目から鱗でした!

    ご提案頂いた方法について質問なのですが、
    ゲームデータをデータベースに持たせる場合のイメージは以下のような感じでしょうか?
    (もちろんゲーム内容によって変わるとは思いますが)
    `
    テーブル:rooms
    カラム:room_id, is_matching, turn

    ロジック部分
    フロントから参加リクエストがあったら
    roomsから空室を探し参加させる
    空室がなければ新たな部屋を作り(insertし)roomsに追加。そこに参加させる。
    ターン数なども毎回roomsから取り出して加算し、roomsのroom_idのturnをupdateする
    `


    また、そのデータベースの構成自体を大幅に変更する場合は
    スマホゲームによくある「メンテナンス時間」をあらかじめ通知して
    サービスを一旦停止する、という実装方法がメジャーでしょうか。

    キャンセル

  • 2019/03/04 18:51 編集

    ソシャゲとか開発したこと無いですし、そういったDBを専門職としてチューニングした経験はないですが、
    (億近いレコードのテーブルを触った経験はあり)

    データベースに持たせるイメージは大体そんなもんですね。
    とはいっても、この辺は作りながらああでもないこうでもないと転がしながら設計していく感じになるでしょう。

    しかし何でもかんでもMySQLで出し入れすると速度面で辛いと思うので、
    ユーザのID、パス、レーティング情報なんかの紛失したら会社を畳みかねないようなデータは紛失しないようMySQLへ、
    ルーム情報や対戦中のデータ(将棋でいう棋譜)なんかはRedisなんかの高速軽量なNoSQLで管理することを考慮に入れましょう。

    まぁ、リリースするまでは全てMySQLで良いと思います。
    実際どこがボトルネックになるかはサービスをリリースしないと見えてこない部分もありますからね。

    > スマホゲームによくある「メンテナンス時間」をあらかじめ通知してサービスを一旦停止する
    設けたほうが良いですね。
    膨大なログファイル、各対戦データ等の情報は凄まじい速度でサーバのHDD領域を奪っていくはずです。
    いくらMySQLみたいなサーバの性能が優れているからといって何年も積み上げるだけだとどんどん速度が劣化してしまうので、3ヶ月より前のデータは別にバックアップとして移動する等した方が良いです。

    そうなると、1週間に数時間メンテナンスの時間が欲しくなるんじゃないですかね?

    キャンセル

  • 2019/03/04 19:40

    非常にわかりやすい説明で助かります。
    データベースへのアクセスによる速度が気になっていましたが
    それも使い分けることで対応できるのですね。

    アドバイス頂いたおかげで次の段階へ進めそうです。
    ありがとうございました!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.59%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る