どうも「バッチサーバ」というのが好きでない。
WEB系をやっていると、WEBサーバ(APサーバも含む)とは別に、
バッチサーバなるものを用意するケースがあるが、
これって実はあんまりいらないと思っている。
(というか、外したいと思っている)
確かに、WEBサーバ側の負荷と切り離して考えられるメリットもある。
しかし、自分にとってはリソースを有効活用できていない感がある。。
最近では、
スクリプト言語でも、WEBサーバ内にバッチ処理用のソースコードを持つことが多々ある。
これは、どのサーバでも代替実行が出来るようにするという狙いからのものであるが、
実際はWEBサーバは複数あるケースが多く、
夜間などのアクセスが少ない時間帯で、
複数サーバで、一気にバッチを実行してしまうほうが処理が速く終わってよいと思う。
javaなんかでは、APサーバ内の1スレッドとして、
バッチ処理を動かすケースなんかもあるだろう。
ということで、こんな風にできたらいいなと思うバッチ処理をまとめてみる。
- WEBサーバ(APサーバも含む)の1スレッドとしてバッチが実行できる
- 手動でも実行できる
- 同一のバッチが複数サーバで動いて欲しくないケースは排他制御をかける
- 空いているリソースを見つけて分散できる
- さらに、実行結果を1か所で確認できる
という感じ。
zookeeperなどの組み合わせることで、
上記を実現できるが、
インフラがやや複雑になり、プログラマーの管理ではなかなか厳しい。。
javaではcron4jなどがあったり、
nodeではcronのモジュールがあったりするので、
上手いことスレッドとして、組み込みつつ、
外側からも実行可能で、分散的に、必要あらば排他的に、
そして、出来るだけ簡素に。
そんなバッチが組めるような仕組みを考えてみたい。
投稿日時:2015年04月14日 17:01
カテゴリー:
server
まとめをしてみます。
- 第1回
- 第2回
- 第3回
- 第4回
- 第5回
- 第6回
- 第7回
という感じにまとまりました。
結局java側だけしかかけなったけど、
javaでは排他制御をかける(synchronizedブロック)がほんとに重要。
nodeはシングルスレッドだから、
排他制御は基本的には不要。
その他、大事なトピックとしては、
- クライアントに送信しようとしても送信できないケースがある(たぶん避けらない)
- バックエンドでDBを使う場合は、基本的にはつなぎっぱなし(ただし、RDBとRedisではちょっと扱いが違うので注意)
などでしょうか。
最後にスケールアウトについて。
単純にスケールアウトする場合、pub/subを使うしか手がない。
pub/subができるのは、
- redis
- ActiveMQ
- Java Message Service
などがあるが、pub/subがあるとないとでは、
実装がかなり異なる。
重要なのは、
「sub」の接続を先に確立して、
処理を行い、
対象者に「pub」して、
「sub」でメッセージ送信を行う
という流れである。
pub/subを行うと、
データ自体もメモリに持てなくなるので、
十分注意しながらやっていただければと思います。
以上で、websocketの話はほんとにおしまい。
投稿日時:2015年04月02日 18:36
カテゴリー:
java,
node.js,
websocket
ラウンド編。
また、websocketの処理だけ書く。
package trmpj.ws;
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.TPair;
import trmpj.TReq;
import trmpj.TSession;
import trmpj.TWs;
import trmpj.req.TReqEnd;
import trmpj.req.TReqGame;
import trmpj.req.TReqPrepared;
import trmpj.res.TResEnded;
import trmpj.res.TResError;
import trmpj.res.TResGame;
import trmpj.res.TResInvalid;
import trmpj.res.TResNone;
import trmpj.res.TResStart;
import trmpj.res.TResWait;
/**
* 対戦用処理
*
*/
@ServerEndpoint(value = "/ws/round/{pairId}")
public class TWsRound extends TWs {
/**
* pairId => TPairのマップ
*/
private static ConcurrentHashMap<String, TPair> pairs = new ConcurrentHashMap<String, TPair>();
/**
* pairId => Threadのマップ
*/
private static ConcurrentHashMap<String, Thread> threads = new ConcurrentHashMap<String, Thread>();
/**
* openハンドラ
* @param pairId
* @param session
*/
@OnOpen
public void onOpen(@PathParam("pairId") String pairId, 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 pairId
* @param session
* @param message
*/
@OnMessage
public void onMessage(@PathParam("pairId") String pairId, 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();
}
// ペアIDに応じたペア情報をマップから取り出す
if (!pairs.containsKey(pairId)) {
pairs.putIfAbsent(pairId, new TPair(pairId));
}
TPair pair = pairs.get(pairId);
// ペア情報に排他制御をかける
// ※重要
synchronized (pair) {
if (req.command.equals(TReq.COMMAND_PREPARED)) {
// preparedコマンド
TReqPrepared reqPrepared = ts.receiveMessage(message, TReqPrepared.class);
if (reqPrepared == null) {
throw new Exception();
}
// 既にペアが揃った状態ならここで終了
if (pair.isPrepared()) {
throw new Exception();
}
// 自分の情報をセッションに追加
ts.setUser(reqPrepared.user);
// ペア情報に自分の情報を追加
pair.addTSession(ts);
// 同一ユーザIDはエラー
if (pair.isDuplicated()) {
throw new Exception();
}
// 2人そろった状態なら、2人にstartを返す
if (pair.isPrepared()) {
// スレッドを止める
threads.remove(pairId);
TResStart resStart = new TResStart();
resStart.users.addAll(pair.getUsers());
pair.sendMessage(resStart);
return;
}
// 揃っていない状態なので、自分にwaitを返す
TResWait resWait = new TResWait();
ts.sendMessage(resWait);
// 別スレッドで、相手がくるまで数秒待機する
if (!threads.containsKey(pairId)) {
threads.putIfAbsent(pairId, new Thread(new TRunnable(pairId)));
threads.get(pair).start();
}
return;
} else if (req.command.equals(TReq.COMMAND_GAME)) {
// gameコマンド
TReqGame reqGame = ts.receiveMessage(message, TReqGame.class);
if (reqGame == null) {
throw new Exception();
}
// 同一ユーザIDはエラー
if (pair.isDuplicated()) {
throw new Exception();
}
// 揃った状態でない、または終了状態なら何もしない
if (!pair.isPrepared() || pair.isEnded()) {
throw new Exception();
}
// 2人に返す
TResGame resGame = new TResGame();
resGame.useId = ts.getUser().userId;
resGame.data = reqGame.data;
pair.sendMessage(resGame);
return;
} else if (req.command.equals(TReq.COMMAND_END)) {
// endコマンド
TReqEnd reqEnd = ts.receiveMessage(message, TReqEnd.class);
if (reqEnd == null) {
throw new Exception();
}
// 同一ユーザIDはエラー
if (pair.isDuplicated()) {
throw new Exception();
}
// ペアを終了状態にする
pair.setEnded(true);
// 自分に返す
TResEnded resEnded = new TResEnded();
ts.sendMessage(resEnded);
return;
}
}
} catch (Exception e) {
e.printStackTrace();
}
// エラー処理
if (ts != null) {
TResError resError = new TResError();
ts.sendMessage(resError);
}
}
/**
* closeハンドラ
* @param pairId
* @param session
* @param reason
*/
@OnClose
public void onClose(@PathParam("pairId") String pairId, 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);
// ペアIDに応じたペア情報をマップから取り出す
if (!pairs.containsKey(pairId)) {
throw new Exception();
}
TPair pair = pairs.get(pairId);
// ペア情報に排他制御をかける
// ※重要
synchronized (pair) {
// 自分の情報削除
pair.removeTSession(ts);
// 空になっていたら削除
if (pair.isEmpty()) {
pairs.remove(pairId);
return;
}
// game中で、endでない場合は、残っている方にnoneを送信
if (pair.isPrepared() && !pair.isEnded()) {
TResNone resNone = new TResNone();
pair.sendMessage(resNone);
return;
}
}
} catch (Exception e) {
e.printStackTrace();
}
// セッション情報も破棄する
sessions.remove(accessdId);
}
/**
* 2人が揃うまで待つ別スレッド
* @author furuta
*
*/
public class TRunnable implements Runnable {
/**
* ペアID
*/
private String pairId;
/**
* コンストラクタ
* @param pairId
*/
public TRunnable(String pairId) {
this.pairId = pairId;
}
/**
* 処理
*/
public void run() {
try {
// 10秒まつ
Thread.sleep(10000);
// ペアを取得
if (!pairs.containsKey(pairId)) {
throw new Exception();
}
TPair pair = pairs.get(pairId);
synchronized (pair) {
if (!pair.isPrepared()) {
// 揃っていない状態であれば、残っている人にinvalidを返す
TResInvalid resInvalid = new TResInvalid();
pair.sendMessage(resInvalid);
}
}
} catch (Exception e) {
e.printStackTrace();
}
// スレッドを削除
threads.remove(pairId);
}
}
}
ポイントはsynchronizedでしっかり排他制御をかけること。
これをやらないとだめだぜ。。
ソースコードはちゃんとテストしてないけど、
処理の流れはわかるはず。。
とりあえず、作成したコードをzipで固めておきます。
ダウンロード
これで、websocketは終了。
nodeは書く気力なし。。
投稿日時:2015年04月02日 18:23
カテゴリー:
java,
node.js,
websocket
やっとかく。
とりあえず、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の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