やっとかく。

とりあえず、websocketのメイン処理をさらす。

 

マッチング

package trmpj.ws;

import java.util.LinkedList;
import java.util.concurrent.ConcurrentHashMap;

import javax.websocket.CloseReason;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import trmpj.TReq;
import trmpj.TSession;
import trmpj.TWs;
import trmpj.req.TReqEntry;
import trmpj.res.TResEntried;
import trmpj.res.TResError;
import trmpj.res.TResPrepare;

/**
 * マッチング用処理
 *
 */
@ServerEndpoint(value = "/ws/matching/{type}")
public class TWsMatching extends TWs {

    /**
     * type => Set<TSession>のマップ
     */
    private static ConcurrentHashMap<Integer, LinkedList<TSession>> entries = new ConcurrentHashMap<Integer, LinkedList<TSession>>();

    /**
     * openハンドラ
     * @param type
     * @param session
     */
    @OnOpen
    public void onOpen(@PathParam("type") int type, Session session) {
        // セッションにアクセスIDを格納する
        String accessdId = generateAccessId();
        session.getUserProperties().put(KEY_ACCESS_ID, accessdId);

        // アクセスIDを、onMessage, onCloseで取得するときの関連を付ける
        // ※sessionsのキー側はsessionでもよいのですが、
        // runnableからの参照時に、セッションが破棄されていると困るので、
        // あえて文字列にしている
        TSession ts = new TSession(session);
        sessions.putIfAbsent(accessdId, ts);
    }

    /**
     * messageハンドラ
     * @param type
     * @param session
     * @param message
     */
    @OnMessage
    public void onMessage(@PathParam("type") int type, Session session, String message) {
        // アクセスIDを取得
        String accessdId = (String) session.getUserProperties().get(KEY_ACCESS_ID);
        TSession ts = null;

        try {
            // アクセスIDをもとに、openハンドラで登録したTSessionを呼び出す
            if (!sessions.containsKey(accessdId)) {
                throw new Exception();
            }
            ts = sessions.get(accessdId);

            // リクエストを解析
            TReq req = ts.receiveMessage(message, TReq.class);
            if (req == null) {
                throw new Exception();
            }

            // タイプに応じたエントリー情報をマップから取り出す
            if (!entries.containsKey(type)) {
                entries.putIfAbsent(type, new LinkedList<TSession>());
            }
            LinkedList<TSession> es = entries.get(type);

            // タイプごとのエントリー情報に排他制御をかける
            // ※重要
            synchronized (es) {
                if (req.command.equals(TReq.COMMAND_ENTRY)) {
                    // entryコマンド
                    TReqEntry reqEntry = ts.receiveMessage(message, TReqEntry.class);
                    if (reqEntry == null) {
                        throw new Exception();
                    }

                    // 自分の情報をセッションに追加
                    ts.setUser(reqEntry.user);

                    try {
                        // 先にエントリーしている人の情報をとる
                        TSession tsPrev = es.pop();

                        // 情報をとった結果、同じユーザIDの場合は、エラーとする
                        if (tsPrev.getUser().userId.equals(ts.getUser().userId)) {
                            throw new Exception();
                        }

                        // エントリーしている人の情報と、自分の情報を、マッチング成立として両者に返す
                        TResPrepare resPrepare = new TResPrepare();
                        resPrepare.type = type;
                        resPrepare.seed = (int) (Math.random() * 10000) + 1;
                        resPrepare.users.add(ts.getUser());
                        resPrepare.users.add(tsPrev.getUser());

                        ts.sendMessage(resPrepare);
                        tsPrev.sendMessage(resPrepare);
                        return;

                    } catch (Exception e) {
                        // ここはpopで例外が発生するが、無視する
                    }

                    // 自分を加える
                    es.add(ts);

                    // 自分にエントリー完了を送信する
                    TResEntried resEntried = new TResEntried();
                    ts.sendMessage(resEntried);
                    return;

                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        // エラー処理
        if (ts != null) {
            TResError resError = new TResError();
            ts.sendMessage(resError);
        }
    }

    /**
     * closeハンドラ
     * @param type
     * @param session
     * @param reason
     */
    @OnClose
    public void onClose(@PathParam("type") int type, Session session, CloseReason reason) {
        // アクセスIDを取得
        String accessdId = (String) session.getUserProperties().get(KEY_ACCESS_ID);
        TSession ts = null;

        try {
            // アクセスIDをもとに、openハンドラで登録したTSessionを呼び出す
            if (!sessions.containsKey(accessdId)) {
                throw new Exception();
            }
            ts = sessions.get(accessdId);

            // 自分が既に登録されていたら、削除する
            if (!entries.containsKey(type)) {
                throw new Exception();
            }
            LinkedList<TSession> es = entries.get(type);
            es.remove(ts);

        } catch (Exception e) {
            e.printStackTrace();
        }

        // セッション情報も破棄する
        sessions.remove(accessdId);
    }
}

最大のポイントは、
onMessageにおけるsynchronizedブロックである。

これでちゃんと排他制御をかけないと、

エラーになるぜ。。

 

ながいので、ラウンド編は次回に。

 

投稿日時:2015年04月02日 18:11   カテゴリー:java, node.js, websocket  

maven3で使うプラグインなのだが、

どうも情報が古かったりするので、

最新版でまとめてみた。

 

【環境】

OS:windows8.1 – 64bit

JDK:1.8 – 64bit

maven:3.2.1 – eclipse lua embedded

 

【プラグイン】

  • maven-compiler-plugin 3.3
  • maven-project-info-reports-plugin 2.8
  • org.apache.maven.plugins 3.4
  • maven-checkstyle-plugin 2.15
  • maven-javadoc-plugin 2.10.2
  • org.apache.maven.plugins 3.4
  • findbugs-maven-plugin 3.0.1
  • maven-jxr-plugin 2.5
  • taglist-maven-plugin 2.4
  • cobertura-maven-plugin 2.7

 

ローカルPCのmavenで各種レポートを作成する場合、

mvn clean site

で作れるのだが、

chekstyleプラグインが独自のcheckstyleファイルを用いていると、

ビルドが失敗する(理由がわからない)

とりあえず、簡単にpomをさらす。

 

ローカルmaven用pom

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  :
  :

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
  </properties>

  <dependencyManagement>
    :
    :
  </dependencyManagement>

  <dependencies>
    :
    :
  </dependencies>

  <build>
    <pluginManagement>
      <plugins>
        <!-- compiler plugin -->
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.3</version>
          <configuration>
            <source>${java.version}</source>
            <target>${java.version}</target>
            <compilerVersion>${java.version}</compilerVersion>
            <encoding>${project.build.sourceEncoding}</encoding>
          </configuration>
        </plugin>
        <!-- project info reports plugin -->
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-project-info-reports-plugin</artifactId>
          <version>2.8</version>
        </plugin>
        <!-- site plugin -->
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.4</version>
          <configuration>
            <inputEncoding>${project.build.sourceEncoding}</inputEncoding>
            <outputEncoding>${project.build.sourceEncoding}</outputEncoding>
          </configuration>
        </plugin>
        <!-- checkstyle plugin -->
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-checkstyle-plugin</artifactId>
          <version>2.15</version>
          <dependencies>
            <dependency>
              <groupId>com.puppycrawl.tools</groupId>
              <artifactId>checkstyle</artifactId>
              <version>6.5</version>
            </dependency>
          </dependencies>
          <configuration>
            <configLocation>${basedir}/config/google_checks.xml</configLocation>
            <encoding>${project.build.sourceEncoding}</encoding>
          </configuration>
        </plugin>
        <!-- javadoc plugin -->
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-javadoc-plugin</artifactId>
          <version>2.10.2</version>
          <configuration>
            <locale>ja</locale>
            <charset>${project.build.sourceEncoding}</charset>
            <encoding>${project.build.sourceEncoding}</encoding>
          </configuration>
        </plugin>
        <!-- pmd plugin -->
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-pmd-plugin</artifactId>
          <version>3.4</version>
          <configuration>
            <outputEncoding>${project.build.sourceEncoding}</outputEncoding>
          </configuration>
       </plugin>
       <!-- findbugs plugin -->
       <plugin>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>findbugs-maven-plugin</artifactId>
         <version>3.0.1</version>
         <configuration>
           <effort>low</effort>
           <xmlOutput>true</xmlOutput>
           <encoding>${project.build.sourceEncoding}</encoding>
           <outputEncoding>${project.build.sourceEncoding}</outputEncoding>
         </configuration>
       </plugin>
       <!-- jxr plugin -->
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jxr-plugin</artifactId>
         <version>2.5</version>
         <configuration>
           <inputEncoding>${project.build.sourceEncoding}</inputEncoding>
           <outputEncoding>${project.build.sourceEncoding}</outputEncoding>
         </configuration>
       </plugin>
       <!-- taglist plugin -->
       <plugin>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>taglist-maven-plugin</artifactId>
         <version>2.4</version>
         <configuration>
           <encoding>${project.build.sourceEncoding}</encoding>
         </configuration>
       </plugin>
       <!-- cobertura plugin -->
       <plugin>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>cobertura-maven-plugin</artifactId>
         <version>2.7</version>
         <configuration>
           <formats>
             <format>html</format>
             <format>xml</format>
           </formats>
           <encoding>${project.build.sourceEncoding}</encoding>
         </configuration>
       </plugin>
     </plugins>
   </pluginManagement>
   <plugins>
     <!-- mvn site -->
     <plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-site-plugin</artifactId>
       <configuration>
       <reportPlugins>
         <plugin>
           <groupId>org.codehaus.mojo</groupId>
           <artifactId>findbugs-maven-plugin</artifactId>
         </plugin>
         <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-project-info-reports-plugin</artifactId>
         </plugin> -->
         <!-- checkstyle はsiteでエラーになるためコメントアウト -->
         <!--
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-checkstyle-plugin</artifactId>
         </plugin>
         -->
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-javadoc-plugin</artifactId>
           <reportSets>
             <reportSet>
               <reports>
                 <report>javadoc</report><!-- テスト用をださないための指定 -->
               </reports>
             </reportSet>
           </reportSets>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-pmd-plugin</artifactId>
         </plugin>
         <plugin>
           <groupId>org.codehaus.mojo</groupId>
           <artifactId>findbugs-maven-plugin</artifactId>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-jxr-plugin</artifactId>
           <reportSets>
             <reportSet>
               <reports>
                 <report>jxr</report><!-- テスト用を出さないための指定 -->
               </reports>
             </reportSet>
           </reportSets>
         </plugin>
         <plugin>
           <groupId>org.codehaus.mojo</groupId>
           <artifactId>taglist-maven-plugin</artifactId>
         </plugin>
         <plugin>
           <groupId>org.codehaus.mojo</groupId>
           <artifactId>cobertura-maven-plugin</artifactId>
         </plugin>
       </reportPlugins>
     </configuration>
    </plugin>
  </plugins>
</build>

</project>

 

上記の状態で、

mvn clean site

でうまいこと、htmlが出力される。

 

で、jenkisでやる場合だが、

これだと実はうまく動かない。

結論としては、

mvn clean checkstyle:checkstyle javadoc:javadoc pmd:pmd pmd:cpd taglist:taglist cobertura:cobertura compile site

がよいかなと。

 

pomは以下。

  <build>
    <pluginManagement>
      : <!--  localの場合と同じ -->
      :
    </pluginManagement>
    <plugins>
    <!-- mvn site -->
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-site-plugin</artifactId>
      <configuration>
        <reportPlugins>
          <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>findbugs-maven-plugin</artifactId>
          </plugin>
        </reportPlugins>
      </configuration>
    </plugin>
  </plugins>
</build>

上記の感じで、findbugsだけ出力の指定をすればよい。

(findbugsは単体でレポートが出せないため)

これで、jenkins上でも各種レポートが上手いこと出力されるはず。

 

※jenkinsのビルド後の処理において、
集計するファイルはちゃんと指定したほうがよい。
Coberturaのxmlが上手く見つけられなかったりする。

 

なげ~~。

 

投稿日時:2015年04月02日 17:51   カテゴリー:java  

redisのようなシングルスレッドのDBは、

極端な話コネクションは1つでよい。

 

javaやnode.jsでwebsocketを組んだとき、

1人1コネクションみたいにやっていたが、

そんな必要もない。

 

たくさんのコネクションを維持しても、

redisはシングルスレッドなので、

一度に処理できる命令は1つ。

(※multiを使っても一つと考える)

 

javaの場合は、スレッドセーフな領域に

redisコネクションを格納しておけばよく、

node.jsの場合は、global変数的な扱いで(※クラス外の定義変数かな?)、

コネクションを格納しておけばよい。

 

実際1つのコネクションでは、切断した時の影響が大きいので、

たとえば、a-z0-9の36個とかでもよいかなと。

 

また、MySQLのようなマルチスレッドでは、

コネクションの共用は気を付けておいたほうがよい。

なぜなら、トランザクションがあるから。

 

コネクションをアプリケーション内に維持できる場合は、

こんな風に考えて実装すると効果的だと思います。

 

apache+phpでは無理だけど、

python+wsgiとかではできそう。(railsはわからん)

 

以上

投稿日時:2015年03月20日 15:19   カテゴリー:redis  

awsのELBを使うと、

websocketの場合、

handshakeから60秒がタイムアウトだと思っていた。

 

しかし、最後の通信(おそらく受信も含む)から60秒がタイムアウトであった。

60秒無通信っていうのは受信も考えるとあまりないかなと。。

 

また、ELBのタイムアウトも延長できるみたいだから、

websocketを通す場合は、タイムアウトを延長するのも

一つの手段にはなるかなと。

 

投稿日時:2015年03月20日 14:30   カテゴリー:aws, websocket  

まだwebsocketのネタが完成しないので、

小ネタを。

 

windowsでjmeterをGUIで起動するとき、

jmeter.bat

なるものをたたく。

 

ただ、こいつをたたくと、

一緒にcommandプロンプトのコンソールがあがるので、

若干わずらわしい。

 

そのため、

jmeter.batをちょっと修正。

 

%JM_START% %JM_LAUNCH% %ARGS% %JVM_ARGS% -jar “%JMETER_BIN%ApacheJMeter.jar” %JMETER_CMD_LINE_ARGS%

start javaw %ARGS% %JVM_ARGS% -jar “%JMETER_BIN%ApacheJMeter.jar” %JMETER_CMD_LINE_ARGS%

 

これで、batを起動してもコンソールがでなくなる。

 

さらに、

Bat To Exe Converter

というソフトを使えば、batをexeファイルに変換してくれるから、

スタートにピン止めできる。

 

以上

投稿日時:2015年02月25日 16:41   カテゴリー:java, jmeter, windows  

ずいぶん間が空いたけど、

javaでwebsocketやるときのサーバ側クラス設計ができました。

↓のような感じです。

websocket_uml

(クラス図あってるかな??)

 

ポイントとしては、

  • websocketのSessionインスタンスを内包するクラスを作る
  • ユーザ情報はセッション内で管理する
  • ペア(自分と相手)の情報を管理するクラスを作成しておく
  • 実際のwebsocket処理では、スレッドセーフなCollectionを使ってインスタンスを管理する

という感じになります。

 

スレッドセーフなCollectionでないと、インスタンスの取り違えが起きますので、ご注意を。

ThreadLocalを使うという手もありますが、

websocketの場合、

  • openハンドラの呼び出しは1回
  • messageハンドラの呼び出しはn回
  • closeハンドラの呼び出しは1回

なので、open時に必要なインスタンスを作成して、closeで破棄するとよいです。

(※messageで作成するときもありますが)

 

実は、websocketではこの考えは結構重要で、

たとえば、DB接続する際、

messageハンドラで、

connect -> disconnect

を繰り返すと、高負荷に耐えられないです。

ここで、

openハンドラでconnectし、closeハンドラでdisconnectする。

そして、messageハンドラで操作する、

という形だと接続のコストが抑えられるので、

結構有効です。

(※もちろん、アプリケーションの特性によりますが)

 

次は、javaのソースコードを公開する予定。

node.js側は気力があったら作成します。。

 

投稿日時:2015年02月19日 15:45   カテゴリー:java, node.js, websocket  

javaからのmail送信ですが、

いろいろ情報が混在しております。

 

ただ、現時点で、

pom.xmlに以下を記載すれば、

javaSEでも動きます。


<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.5.0-b01</version>
</dependency>

 

以上

 

投稿日時:2015年01月29日 17:54   カテゴリー:java  

前回、awsにtd-agentを入れて、

s3→redshiftとの連携を図ったけど、

さらにうまくやる方法を記載しておく。

 

●カラムを超えた場合のデータについて

TRUNCATECOLUMNS

オプションを付けておくことで回避できる

 

●redshiftに取り込まれた日時を登録する方法

テーブル側にカラムを追加して、jsonpathとのカラムミスマッチを防ぐ

 

具体的には、

CREATE TABLE test_access_logs (
    date            datetime sortkey,
    ip              varchar(64),
    header          varchar(256),
    code            varchar(16),
    size            varchar(16),
    aaa             varchar(64),
    bbb             varchar(128),
    ccc             varchar(128),
    second          varchar(16),
    insert_date     datetime  not null default convert_timezone('JST', getdate())
);

上記みたいに、insert_dateを追加する。

 

ただし、このまま取り込むと、

{
    "jsonpaths": [
        "$['date']",
        "$['ip']",
        "$['header']",
        "$['code']",
        "$['size']",
        "$['aaa']",
        "$['bbb']",
        "$['ccc']",
        "$['second']"
    ]
}

カラムのミスマッチがおきてしまう。

 

これを回避するために、

# COPY test_access_logs
  (date, ip, header, code, size, aaa, bbb, ccc, second)
  FROM 's3://MY-BUCKET/logs/httpd/201501/26/'
  credentials 'aws_access_key_id=XXX;aws_secret_access_key=YYY'
  JSON AS 's3://MY-BUCKET/logs/httpd/test_access_log_jsonpath.json'
  GZIP
  COMPUPDATE ON
  RUNCATECOLUMNS;

上記のように、カラム指定で取り込むと、

insert_dateはnullになり、defaultが適用される。

 

なぜこのような方法をとるかというと、

もし再取り込みが発生した場合に、

以前のデータを消さないで取り込むと、

重複で取り込まれてしまう。

重複を回避するには、

以前取り込んだデータを削除しなければならない。

insert_dateを登録しておけば、

以前登録したデータを検索できる。

 

ちなみに、redshift上で、日本時間を取得するには、

# select convert_timezone(‘JST’, getdate());

# select dateadd(hour, 9, getdate());

のような方法がある。

 

以上

投稿日時:2015年01月29日 11:02   カテゴリー:aws, server  

awsにtd-agentを導入して、

httpのアクセスログと、phpの任意のログを、

td-agent -> s3 -> redshift

の流れで投入してみた。

awsのEC2ではtd-agentのバージョン2の導入が大変なので、

バージョン1を導入してみた。

 

インストールは公式ドキュメントにある通り、以下です。

$ curl -L http://toolbelt.treasuredata.com/sh/install-redhat.sh | sh

※v2はcentos7での動作は確認しましたが、awsではv1を利用

 

いろいろ情報が混在していたのですが、

S3プラグインは最初から入っていて、

フォーマットはjsonでS3に送信することが可能です。

こんな感じ。

 

/etc/td-agent/td-agent.conf

<source>
    type tail
    path /var/www/test/logs/access.log
    tag apache.test.access
    pos_file /var/log/td-agent/test.access.log.pos
    format /^\[(?<date>.*?)\]\[(?<ip>.*?)\]\[(?<header>.*?)\]\[(?<code>.*?)\]\[(?<size>.*?)\]\[(?<aaa>.*?)\]\[(?<bbb>.*?)\]\[(?<ccc>.*?)\]\[(?<second>.*?)\]$/
</source>
<match apache.test.access>
    type s3
    aws_key_id XXX
    aws_sec_key YYY
    s3_bucket MYBUCKET
    s3_endpoint s3-ap-northeast-1.amazonaws.com

    path logs/httpd/
    buffer_path /var/log/td-agent/httpd/test-access.log
    time_slice_format %Y%m/%d/test-acccess.log.%Y%m%d%H%M
    retry_wait 30s
    retry_limit 5
    flush_interval 1m
    flush_at_shutdown true
    format json
</match>

 

 

※httpdアクセスログのフォーマットは、

LogFormat “[%{%Y-%m-%d %H:%M:%S %Z}t][%h][%r][%>s][%b][%{X-AAA}i][%{X-BBB}i][%{X-CCC}i][%T]” hoge

です。

 

でもって、S3には

test-acccess.log.201501270938_0.gz

こんな感じのログが保存されている。

中身は、

{“date”:”2015-01-27 09:38:10 JST”,”ip”:”182.118.55.177″,”header”:”GET / HTTP/1.1″,”code”:”200″,”size”:”11″,”aaa”:”-“,”bbb”:”-“,”ccc”:”-“,”second”:”0″}

みたいな感じ。

 

んでもって、

s3上にlogs/httpd/test_access_log_jsonpath.jsonってファイルを作成しておく

{
    "jsonpaths": [
        "$['date']",
        "$['ip']",
        "$['header']",
        "$['code']",
        "$['size']",
        "$['aaa']",
        "$['bbb']",
        "$['ccc']",
        "$['second']"
    ]
}

 

そして、テーブルを作成しておく。

CREATE TABLE test_access_logs (
    date    datetime sortkey,
    ip        varchar(64),
    header varchar(256),
    code    varchar(16),
    size      varchar(16),
    aaa       varchar(64),
    bbb       varchar(128),
    ccc        varchar(128),
    second varchar(16)
);

 

この状態でredshift上から、

# COPY test_access_logs
  FROM 's3://MY-BUCKET/logs/httpd/201501/26/'
  credentials 'aws_access_key_id=XXX;aws_secret_access_key=YYY'
  JSON AS 's3://MY-BUCKET/logs/httpd/test_access_log_jsonpath.json'
  GZIP
  COMPUPDATE ON;

ってやれば、あらかじめ作成しておいたテーブルにデータが突っ込まれる。

 

上記のcreateでは、圧縮オプションはつけていないが、

繰り返しが多い項目に圧縮オプションつけておくと、

かなり削減になる。

 

【参考】

1000万件のデータを突っ込んだ際、元容量の70%くらいになった。

(あくまで一例ね。)

だた、アクセスログなんかは、比較的繰り返しが多い項目もあるので、

その辺は効果的にやるとよいかもね。

 

投稿日時:2015年01月27日 17:51   カテゴリー:aws, server  

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