websocketにおいて、

node.jsでpm2を使う際の問題点をまとめてみる。

 

バージョンは、

<node.js>

v0.10.xx

<pm2>

v0.11.xx

v0.12.xx

 

上記の場合、pm2のバージョンに関わらず、

  • windowsでは使えない
  • clusterモードで再起動(restart)を行うと、ポート開放されない(→killする必要あり)
  • GodDaemonを起動したユーザ以外では操作不可能(pm2 listでもGodDaemonが起動するので要注意)
  • 再起動の抑制オプションがない

という感じです。

 

clusterモードの最悪な点として、

1.アプリが落ちる⇒2.再起動⇒3.ポート開放されずにアクセス不可能

となる点かなと。

これが4プロセス起動していたら、

1プロセスのダウンが全プロセスに影響すること。

つまり、

pm2 start app.js -i 4

なんてして、4プロセス起動しても、

1プロセスダウンすれば、もうおしまい。

1つのプロセスの再起動が、他プロセスにも影響して、アクセス不可能になる。

再起動の抑止オプションがあれば、まだいけそうな気もするが。。。

回避するには、

node自体をv.11.xx(現時点でunstable)にすればいけるようだが、未検証なので、何とも言えない。。。

 

じゃあ、forkモードで起動すればいいじゃんとなりますが、

forkの場合は、プロセスとポートが1対1なので、

4プロセス起動するには、4ポート必要になりますので、柔軟性は低くなる。

ただ、forkモードでは、再起動時にポート開放されますので、

node.jsの前に置くフロントサーバをコントロールできれば問題はないかなと。

 

まだまだ、node.jsは発展途上なわけなので、

このあたりのリスクを十分に踏まえる必要がありますね。

 

余談ですが、websocketは再起動が命取りになるので、

foreverでポート分散して、再起度を抑止するようにしておくほうが

現在取りうる手段としてはベターかなと感じています。

 

投稿日時:2015年01月13日 12:47   カテゴリー:node.js, websocket  

nicを2本差しのゲートウェイサーバを構築していたのですが、

久しぶりにlinuxとハードウェアではまったので、

記録しておく。

 

【パターン1】

・centos6.6 + pcie x1 Marvel 88E8053

nicは大手量販店で購入したもの。

pcieのボードはこれしかなかった。。

→(結果)ドライバーをダウンロードして(ここから)、ビルドするものの、

ドライバーはロードできているが、nic自体を認識せず。

 

【パターン2】

・centos7.0 + pcie x1 Marvel 88E8053

→(結果)ドライバーのビルド自体失敗。

 

【パターン3】

・ubuntu14LTS + pcie x1 Marvel 88E8053

→(結果)ドライバーのビルド自体失敗。

 

どうやら、このドライバーは、

カーネル3系には対応していないようであった。

 

【パターン4】

・centos7.0 + buffalo LUA3-U2-ATX

→(結果)刺した瞬間うまくいった。

100MBPSだけどね。。

 

最近はlinuxでもかなりハードを認識するから

行けるかと踏んでいたが、

そんなこともないものでした。

 

ちなみに、2本刺しと書きましたが、

1本はオンボードで、こいつは問題なしでありました。(broadcom製)

 

投稿日時:2014年12月19日 14:19   カテゴリー:server  

よく忘れるので、

virtualboxのネットワーク設定をメモ。

 

【目標】

  • ホストOSは固定IP(192.168.1.10)
  • ゲストOSは固定IP(192.168.1.11)
  • ホストOSとゲストOSは同一ネットワーク(255.255.255.0)
  • 同一ネットワーク内から、ゲストOSへ接続可能
  • ゲストOSは外部へ接続可能(※yumとかできないからね)

 

【手順】

  1. virtualboxのインストール
  2. ホストオンリーアダプターと、wifi(ホストが使っている)をブリッジ
  3. ブリッジしたアダプターのIPをwifiで使っていたものに変更(192.168.1.10)
  4. ゲストOSのネットワーク設定で、「ホストオンリーアダプター」を選択
  5. ゲストOSをインストールし、固定IP(192.168.1.11)を割り当てる

 

起動後、

ホストOSからゲストOSへssh接続で確認。

ゲストOSからpingを発行して、ホスト側(またはインターネット)につながればOK。

 

以上

 

投稿日時:2014年12月11日 15:32   カテゴリー:virtualbox  

firefoxで開発に役立つプラグインを紹介します。

 

●Firebug

言わずと知れたデバッカー。

chromeよりやりやすいと思うのは自分だけか?

 

●FireMobileSimulator

携帯のUser-Agent等を設定できるシュミレーター。

今はあんまりつかわないけど。

 

●Live HTTP headers

たまにつかう。

ページを遷移しても、ヘッダーをキャプチャーしてくれるので、

Firebugをちょっとだけ補える。

 

●Modify Headers

拡張ヘッダーを送るときに使う。

chromeのDev Http Clientってのより、

ブラウザ感覚で送れてよいかな。

 

●Pearl Crescent Page Saver Basic

たまに画面キャプチャーをとるときに使う。

スクロール先も含めてキャプチャーしてくれるので、

そこがかなりうれしい。

 

投稿日時:2014年12月09日 16:03   カテゴリー:firefox  

jdk7からjdk8に変えた時、

Runnableのスコープ外で宣言した変数が、

Runnable内でfinalを付けないで参照できた。

 

java7だと、


int numberLocal = 1;

Thread t = new Thread(new Runnable() {

    final int numberThread = numberLocal;

};

java8だと、


int numberLocal = 1;

Thread t = new Thread(new Runnable() {

    int numberThread = numberLocal;

};

のような感じで、java8だとfinalが不要らしい。

ただし、書き換えるとエラーになるので、

暗黙的なfinalということになるようです。

 

投稿日時:2014年12月09日 15:36   カテゴリー:java  

前回ゲーム仕様を決めたので、

今回は詳細は処理フローを作成します。

以下のような感じを想定しています。

websocket_seq

上記イメージの解説です。

  1. マッチングサーバと接続します。
  2. エントリーに必要な情報をおくります。サーバ側が1人目の受付の場合、「受付完了」という情報を返します。もし、ここで、2人目なら、「成立完了」という情報を2人に返します。
  3. 「成立完了」を受け取ったクライアントは、マッチングサーバから抜けて、対戦サーバ側と接続します。
  4. 対戦サーバと接続したら、「準備完了」を送ります。ここで、2人がそろって初めて「開始」を返します。
  5. ゲーム中は、片側(相手)から送られた情報を、2人に送信します。
  6. 無事ゲームが終わったら(終了判断はクライアントに依存)、1人1人が別々に「終了」を送信し、「終了」を受け取ったら各人で、対戦サーバから抜けます。
  7. もし、ゲームが始まっている状態で、片側(相手)が切断したら、残っている方に「相手が切断した」という情報を送ります。今回の仕様では、相手の勝利となり、相手側は「終了」を送信します。

ポイントは4、6、7です。

4のとき、相手が来ないケースもあるので、制限時間以内に相手が来なかったらどのような情報を返すかをサーバ側で対応する必要があります。

6はなぜ2人に送信しないかというと、下手に2人に送信してしまうと、終了時のクライアントの表示を片側が変更可能となってしまうからです。

7は片方の切断時に、残っている方へ何かしらの通知を行う対応です。

 

pub/subを使わない前提である以上、

上記の操作をすべてメモリ上の変数にて制御をかけます。

(※当然サービスのレベルでは、何かしらのデータベースへ書き込む等がありますが、ここでは考慮しません。)

 

次は、サーバ側のクラス設計を書きます。

 

投稿日時:2014年12月01日 12:43   カテゴリー:java, node.js, websocket  

プレビュー版のようですが、

MySQLと互換性をもつRDSが発表されましたね。

詳しくは、こちら

 

ちょっと気になるのは、

AmazonのEC2で選択できるAmazonLinuxって、centos6系をベースにカスタマイズされたものだったような。

 

centosは7から、標準レポジトリにMySQLが外れて、mariadbになってたから、

当面はAmazonLinuxはcentos7には上げないのかね?

 

EC2とRDSで違うから、問題ないのかもしれないけど、

mariadbもaws(特にRDS)で使えるようになると、今後もっと流行りそうな気がします。

 

投稿日時:2014年11月26日 13:16   カテゴリー:aws, mariadb, mysql  

今のところ、3回にわたりwebsocketの比較を書いてきましたが、

これからは、

  • 処理フローの設計
  • プログラミング

となります。

 

とその前に、一服。

 

個人的な見解としては、

node.jsよりjavaの方がwebsocketは組みやすいかなと思っています。

理由は、

  • 過去資産が豊富なので、選択肢が多い
  • 型制約があるので、全体的に堅牢になる
  • eclipseなどのIDEが優秀

といったところでしょうか。

node.jsはjavascriptなので、

なんでもできてしまう分(たとえば、ユーザ定義オブジェクトになんでも突っ込めるとか)、

コードがわかりにくくなりがちです。(※規約で気を付けないとね)

また、IOが基本的に非同期で実行されるとかが、

やっぱりやりにくいなと感じてます。

ただ、java側はAPサーバを動かすコスト(メモリ消費)がやや高いので、

省エネなのはnode.jsかなとも思います。

 

ちなみに、javaでwebsocketをやる場合、

jetty8とかは独自実装になっており、

jetty9でJSR356に対応してます。

※jetty9の場合、jettyの独自パッケージの中で、JSR356に読み替えているようです。mavenのpomファイルも後で作成します。

JSR356に対応しておけば、tomcatに移植しても動くので、独自実装はもう使わないほうがよかです。

なお、あのplayframework2.3のwebsocketはjetty8でした。(playは機能面はすごいけど、ちょっと重いよね。。)

 

最終的には、どちらもクセはあります。

取り巻く環境、開発者の経験等を考慮して、

選択するとよいと思います。

(go言語なんかでもやっている人はいるのかな?goはこれから勉強します。)

 

投稿日時:2014年11月26日 13:04   カテゴリー:java, node.js, websocket  

前回の続き。

前回の大切な前提として、

pub/subはなし(つまり、メモリ内で処理を完結可能)

というのがありました。

 

実際に対戦ゲームの作成を通して、処理フローを整理しましょう。

別に面白いものを作る必要はないので、

簡単なゲームとして、以下のような仕様とします。

  1. 2人対戦の神経衰弱
  2. ゲームの種別として、4×4、4×8、4×12の3種類(使用枚数が16枚、32名、48枚)
  3. ユーザはマッチング時に、ゲーム種別を選択可能
  4. マッチングが成立したらゲーム開始
  5. ゲーム中に離脱した場合、その時点で残っている人の勝利
  6. 離脱者なしにゲームが終わった場合、自分の取得枚数が多ければ勝利で、少なければ敗北

 

pub/subはないので、

スケールアウトの手段が現状皆無です。

そこで、URLを絡めて、効果的にスケールアウトができるようにしておきましょう。

URLはこんな感じです。

【マッチング】 /ws/matching/${type}

${type}とは、上記の4×4などをコード化したもの

【対戦】 /ws/round/${pairId}

${pairId}とは対戦者同士を結びつける一意のキーで、形式は0-9a-zの繰り返し

 

こんな感じでURLを設計しておくことで、

websocketのリバースプロキシ可能なサーバ(nginxやapache2.4など)で、

URLに応じた振り分けが可能となります。

この設計方式だと、マッチングが成立したら、

一旦マッチングサーバとの接続を閉じて、

新たに対戦サーバと接続する必要があります。

 

※nginxの設定例

vim /etc/nginx/websocket_params

proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Document-URI $document_uri;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";

vim /etc/nginx/conf.d/game.conf


# マッチング4x4
upstream ws_matching_4x4 {
    server localhost:8081;
    server localhost:8082 backup;
}
# マッチング4x8
upstream ws_matching_4x8 {
    server localhost:8083;
    server localhost:8084 backup;
}
# マッチング4x12
upstream ws_matching_4x12 {
    server localhost:8085;
    server localhost:8086 backup;
}

# 対戦(ペアIDの先頭がcからnまでの12個)
upstream ws_round_0b {
    server localhost:8181;
    server localhost:8182 backup;
}
# 対戦(ペアIDの先頭がcからnまでの12個)
upstream ws_round_cn {
    server localhost:8183;
    server localhost:8184 backup;
}
# 対戦(ペアIDの先頭がoからzまでの12個)
upstream ws_round_oz {
    server localhost:8185;
    server localhost:8186 backup;
}

server {
    listen 80;
    server_name game.example.com;

    charset utf-8;
    root /var/www/game/public;

    # エラーページ
    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;

    # websocket_paramsのインクルード
    include /etc/nginx/websocket_params;

    # マッチング4x4
    location ~ ^/ws/matching/4/$ {
        proxy_pass http://ws_matching_4x4;
    }
    # マッチング4x8
    location ~ ^/ws/matching/8/$ {
        proxy_pass http://ws_matching_4x8;
    }
    # マッチング4x12
    location ~ ^/ws/matching/12/$ {
        proxy_pass http://ws_matching_4x12;
    }

    # 対戦(ペアIDの先頭がcからnまでの12個)
    location ~ ^/ws/round/[0-9a-b][0-9a-z]+?/$ {
        proxy_pass http://ws_round_0b;
    }
    # 対戦(ペアIDの先頭がcからnまでの12個)
    location ~ ^/ws/round/[c-n][0-9a-z]+?/$ {
        proxy_pass http://ws_round_cn;
    }
    # 対戦(ペアIDの先頭がoからzまでの12個)
    location ~ ^/ws/round/[o-z][0-9a-z]+?/$ {
        proxy_pass http://ws_round_oz;
    }
}

※全部localhostにしてポートを変更していますが、細分化すれば、上記設定で12台に切り分けられます。

※もちろん、もっと細かく分けることも可能です。

 

という具合で考えると、

ちょっと大げさに以下のようなイメージでインフラが考えられます。

websocket_struct

 

 

このようにフロントのサーバの機能を利用することで、

pub/subを使わずにwebsocketを通じたメッセージのやり取りができます。

狙いとしては

同一属性者を同じブロセスに閉じ込めることで、メモリ内の操作で処理を完結させる

ということになります。

 

投稿日時:2014年11月25日 18:17   カテゴリー:java, node.js, websocket  

airアプリをStalingFrameworkで作っていた時、

StalingFrameworkに制御が移る瞬間(stage3Dに制御が移る瞬間でよいのかな?)に、

描画イベントが効かなくなるという問題があった。

簡単なコードは以下。

public class Main extends Sprite
{
    /**
     * starlingのエンジン
     */
    private var _starling:starling.core.Starling;

    /**
     * コンストラクタ
     */
     public function Main():void
     {
         stage.scaleMode = StageScaleMode.NO_SCALE;
         stage.align = StageAlign.TOP_LEFT;

         // 基本画面領域の定義
         var width:int = 640;
         var height:int = 960;

         // 基本領域
         var base:Rectangle = new Rectangle(0, 0, width, height);

         // フル領域
         var full:Rectangle = new Rectangle(0, 0, stage.fullScreenWidth, stage.fullScreenHeight);

         // 表示領域
         var viewPort:Rectangle = starling.utils.RectangleUtil.fit(base, full);

        // ※ここ以降に制御が移ったら、それ以前に定義しておいた描画イベントは効かなくなる
        // starlingのエンジンを生成
        _starling = new starling.core.Starling(Game, stage, viewPort);
        _starling.stage.stageWidth = width;
        _starling.stage.stageHeight = height;
        _starling.simulateMultitouch = false;
        _starling.addEventListener(starling.events.Event.ROOT_CREATED, function(e:*, game:Game):void
        {
            // ゲーム開始
            _starling.start();
        });
    }
}

自分の場合、iosでスプラッシュ画面がすぐに終了してしまい、

Starlingの初期化と同時に音声やら画像を一気にロードしていた時間がごまかせなくなった結果、

「now loading」的な描画処理をEnterFrameで入れていたら、

それが動かなくなったというものでした。

 

投稿日時:2014年11月25日 17:08   カテゴリー:air, as3