新しいネタがないので、
過去の小ネタを。
phpでデーモンプログラムを組んで、
そこで、pdoを使う場合、
fetchを使うと処理が落ちる。
fetchAllでないとダメ。
理由は、、、忘れた。。
IT系のめもを蓄積していこうかと
新しいネタがないので、
過去の小ネタを。
phpでデーモンプログラムを組んで、
そこで、pdoを使う場合、
fetchを使うと処理が落ちる。
fetchAllでないとダメ。
理由は、、、忘れた。。
負荷試験をやっていると、
他人の状態を保持して、
その状態に応じて処理を行うということがある。
たとえば、
という場合など。
これをjmeterで試験する場合、
「BさんがAさんの友達申請を許可する」という処理は、
「AさんがBさんに友達の申請をする」が完了していないと成功しない。
そのためには、BさんはAさんの状態を取得する必要がある。
ここで、
AさんのIDを1
BさんのIDを2
とし、
jmeterのBSF(javascript)を使うと、
<(PostProcesser)AさんがBさん友達申請をする>
// 自分のID var my_id = 1; // 他人のID var other_id = 2; // レスポンスを取得 して申請が成功しているなら // Bさんの情報にAさんが申請したことを記録 var key = "appley-to-" + other_id; var value = my_id; props.put(key, value);
<(PreProcesser)BさんがAさんの友達申請を許可する>
// 自分のID
var my_id = 2;
// 他人のID
var other_id = 1;
// BさんがAさんからの申請状態を取得
var key = "appley-to-" + my_id;
while (true) {
var value = props.get(key);
if (value != void(0)) {
// 申請状態がとれたら、ループを抜ける
break;
}
}
// 許可処理を実行
という具合になる。
注意点としては、
jmeterが起動中は、
propsの値は常に保持されるので、
不要になったら、removeしておく必要がある。
もしくは、初期化処理でremoveしてから利用するなど。
もう1つ注意点としては、
永久ループで待機する場合は、
スレッドを停止しても処理が停止しないので、
何回かループしたら止めるとかを入れておいた方がよい。
あとは、カウンタとか、forEachループなどを組み合わせることで、
もっと複雑な処理が可能になる。
以上です。
まあ、小ネタだね。
実行可能warをmavenで作る場合、
warパッケージの直下にmainファイルを配備する必要がある。
しかし、warアプリケーションをpackageすると、
WEB-INF/classes以下にmainファイルも配備されてしまう。
そのため、maven3では、
antrunプラグインを用いて、mainファイルを移動する必要がある。
pom.xmlはこんな感じかな。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>move-main</id>
<phase>prepare-package</phase>
<configuration>
<tasks>
<move todir="target/${project.build.finalName}">
<fileset dir="target/classes">
<include name="app/AppMain.class" />
</fileset>
</move>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
これにより、warファイル直下に実行可能クラスが配備される。
また、jettyなどのembedを組み込む場合、
一緒に依存関係も持って行ってやる必要がある。
こんな感じ。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>jetty-classpath</id>
<phase>prepare-package</phase>
<goals>
<goal>unpack-dependencies</goal>
</goals>
<configuration>
<includeGroupIds>org.eclipse.jetty,javax.servlet,javax.websocket</includeGroupIds>
<excludes>META-INF/ECLIPSEF.*</excludes>
<outputDirectory>${project.build.directory}/${project.build.finalName}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
そして、mainファイルはこんな感じ。
package app;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
import java.net.URL;
public class AppMain {
public static void main(String[] args) throws Exception {
// 初期設定
int port = 8080;
System.setProperty("prefix", "/");
// サーバ生成
Server server = new Server(port);
// warを読み込むコンテクスト生成
URL warUrl = AppMain.class.getProtectionDomain().getCodeSource().getLocation();
String warLocation = warUrl.toExternalForm();
WebAppContext context = new WebAppContext();
context.setWar(warLocation);
context.setContextPath("/");
// コンテクストにサーバをセットする
// ※重要
context.setServer(server);
// websocketを使えるようにする
WebSocketServerContainerInitializer.configureContext(context);
// 開始
server.setHandler(context);
server.start();
server.join();
}
}
上記が非常に重要。
これで、websocketのendpointをServletContextListener側で指定してやるとうまくいく。
package app;
import websocket.WebsocketIndex;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.websocket.DeploymentException;
import javax.websocket.server.ServerContainer;
public class AppInitializeListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
// embed用のwebsocketの追加
try {
ServerContainer wsContainer = (ServerContainer) sce.getServletContext().getAttribute(ServerContainer.class.getName());
System.out.println(String.format("wsContainer => %s", wsContainer));
if (wsContainer != null) {
wsContainer.addEndpoint(WebsocketIndex.class);
}
} catch (DeploymentException e) {
e.printStackTrace();
}
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
実行可能jarでwebsocketを作成する場合、
mainクラス内でendpointを設定してやればよいのだが、
実行可能warの場合、
mainクラスが実際のwebsocketのendpointのクラスの配備位置が異なるため、
クラスのロードが出来ない状態となる。
そのため、上記のような方法で、動的に指定してやることで、
実行可能warにおいて、websocketが利用できる。
【参考】
・実行可能warの生成の詳細がかかれています。
http://qiita.com/k_ui/items/1d3bbbd7993c4c9adf71
・実行可能jarでのwebsocketの設定方法が書かれています。
以上
ちまたでは、chefやらansibleといった
インフラ構成ツールが流行っている。
しかし、個人的にはどっちもめんどくさい。
ドキュメントは充実しているが、
それなりに扱うには独自の書き方を習得する必要がある。
そこで、以下のように考えてみた。
以上があれば十分で、あと必要なものは、
ってくらいだろうか。。
そして、「冪等性」ってのを保つのは非常に重要。
これらを踏まえつつ、
基本的にはシェルスクリプトを作成し、
WEBベースで、実行・管理できればよいかなと思っている。
そんなツールに着手してみようかな。。
どうも「バッチサーバ」というのが好きでない。
WEB系をやっていると、WEBサーバ(APサーバも含む)とは別に、
バッチサーバなるものを用意するケースがあるが、
これって実はあんまりいらないと思っている。
(というか、外したいと思っている)
確かに、WEBサーバ側の負荷と切り離して考えられるメリットもある。
しかし、自分にとってはリソースを有効活用できていない感がある。。
最近では、
スクリプト言語でも、WEBサーバ内にバッチ処理用のソースコードを持つことが多々ある。
これは、どのサーバでも代替実行が出来るようにするという狙いからのものであるが、
実際はWEBサーバは複数あるケースが多く、
夜間などのアクセスが少ない時間帯で、
複数サーバで、一気にバッチを実行してしまうほうが処理が速く終わってよいと思う。
javaなんかでは、APサーバ内の1スレッドとして、
バッチ処理を動かすケースなんかもあるだろう。
ということで、こんな風にできたらいいなと思うバッチ処理をまとめてみる。
という感じ。
zookeeperなどの組み合わせることで、
上記を実現できるが、
インフラがやや複雑になり、プログラマーの管理ではなかなか厳しい。。
javaではcron4jなどがあったり、
nodeではcronのモジュールがあったりするので、
上手いことスレッドとして、組み込みつつ、
外側からも実行可能で、分散的に、必要あらば排他的に、
そして、出来るだけ簡素に。
そんなバッチが組めるような仕組みを考えてみたい。
まとめをしてみます。
という感じにまとまりました。
結局java側だけしかかけなったけど、
javaでは排他制御をかける(synchronizedブロック)がほんとに重要。
nodeはシングルスレッドだから、
排他制御は基本的には不要。
その他、大事なトピックとしては、
などでしょうか。
最後にスケールアウトについて。
単純にスケールアウトする場合、pub/subを使うしか手がない。
pub/subができるのは、
などがあるが、pub/subがあるとないとでは、
実装がかなり異なる。
重要なのは、
「sub」の接続を先に確立して、
処理を行い、
対象者に「pub」して、
「sub」でメッセージ送信を行う
という流れである。
pub/subを行うと、
データ自体もメモリに持てなくなるので、
十分注意しながらやっていただければと思います。
以上で、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は書く気力なし。。
やっとかく。
とりあえず、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ブロックである。
これでちゃんと排他制御をかけないと、
エラーになるぜ。。
ながいので、ラウンド編は次回に。
maven3で使うプラグインなのだが、
どうも情報が古かったりするので、
最新版でまとめてみた。
【環境】
OS:windows8.1 – 64bit
JDK:1.8 – 64bit
maven:3.2.1 – eclipse lua embedded
【プラグイン】
ローカル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が上手く見つけられなかったりする。
なげ~~。
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はわからん)
以上