まつざきの技術メモ

エンジニアの私的な技術メモです。


コメントする

福岡JavaEE勉強会(第1回)を開催しました

福岡JavaEE勉強会(第1回)を開催しました。

ハンズオンの予定だったのですが、JavaEEの概要説明に時間を取り過ぎてしまったのと、
DBの準備に手間取ってしまい、コードを書く時間が取れませんでした。
準備が悪くてすんません><

今回使用したアプリのソースコードはこちらです。
ISUCON2というチューニングコンテストのアプリをJavaEE7のJAX-RS+Thymeleafで実装したものになります。

僕自身、業務でここ1年位JavaEE6(JSF)を使っているのですが、
以前からJAX-RSとThymeleafの組み合わせが気になっていたのと、
2013年6月にリリースされたJavaEE7をまだ試せていなかった事もあり、
JavaEE7のJAX-RS+Thymeleafで実装してみました。

■勉強会の準備をする時に参考にしたブログエントリーやスライド
Java EE 7 リリース 2013/06/12

Maven 入門

Tutorial: Using Thymeleaf
デザイナーさんたちとの協業を推し進める THYMELEAF
Thymeleaf の Document 読みながら Memo

JavaでIDEのアクセッサ生成よりlombokを使ったほうがいい理由

JAX-RSとかの話
Thymeleaf + JAX-RS + DomaをGlassFishで試してみる
JAX-RSとThymeleafの組み合わせをためしてみた
JAX-RSはHTML Webアプリケーションを開発するのに充分なフレームワークであるか?

[JPA2.0]キャッシュ
JPQL速かった!~JPAクエリ表現ごとのパフォーマンス比較 その2
JPQLを利用する際に行っていること
JPAのキャッシュを使ったアプリケーション高速化手法
Java EE 7のTransactionalアノテーションを試してみる

CDI関連情報まとめ
Java EE環境におけるCDIのデフォルト化
CDIを使う

Java EE関連の書籍あれこれ

 

主宰の@itoKami1123さん、ご参加頂いた皆様、
会場をご提供いただいたmswave福岡校様ありがとうございましたm(_ _)m

 

懇親会で「JavaFXとかPlayとかの勉強会はやらないの?」という話が出ました。
僕も勉強会したいので、福岡でやっている方いらっしゃいませんか?


コメントする

GlassFishが付加したExpiresヘッダを上書きする

Apache + GlassFishの環境で稼働中のアプリで、
アプリ内のjsファイルを修正しても、内容がクライアントに反映されなかった件の対応メモ。

GlassFishが静的ファイルにExpiresヘッダを付加するのが原因だったので、
(webapp/resourcesに配置したファイルだけ?)
今回はその前段に配置しているApacheのmod_expiresモジュールで対応しました。

GlassFishで設定出来そうな気がしますが、設定項目を見つけきれませんでした。
ご存知の方がいらっしゃれば教えて下さい><

■バージョン
OS: Scientific Linux 6.3 x86_64
Apache: 2.2.15
GlassFish: 3.1.2.2

■手順
Apacheはyumでインストール済なので、
ビルドしたRPMファイルからモジュールファイルを取り出して差し替えました。

1. 依存パッケージをインストール
2. 作業ディレクトリを作成
3. SRPMをダウンロード
4. mod_expiresのパッチファイルを作成
5. specファイルを修正
6. .rpmmacrosファイルを作成
7. RPMファイルをビルド
8. RPMファイルを解凍して、モジュールファイル「mod_expires.so」を取り出す
9. モジュールファイルを差し替え

SRPMの作成は必ず一般ユーザで行なうようにしてください。

# yum install rpm-build apr-devel apr-util-devel pcre-devel

$ mkdir -p ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}

$ cd ~/rpmbuild/SRPMS

$ yumdownloader --source httpd

$ rpm -ivh httpd-2.2.15-28.sl6.src.rpm

$ cat << EOF > ~/rpmbuild/SOURCES/httpd-2.2.15-mod_expires.patch
--- httpd-2.2.17.orig/modules/metadata/mod_expires.c    2008-11-12 04:59:22.000000000 +0900
+++ httpd-2.2.17/modules/metadata/mod_expires.c 2011-01-20 11:47:03.000000000 +0900
@@ -472,6 +472,11 @@
         expiry = apr_table_get(r->headers_out, "Expires");
         t = r->headers_out;
     }
+    if ( apr_table_get(r->subprocess_env, "override_expires") ) {
+        expiry = NULL;
+        apr_table_unset(t,"Expires");
+        apr_table_unset(t,"Cache-Control");
+    }
     if (expiry == NULL) {
         /*
          * No expiration has been set, so we can apply any managed by
EOF

$ emacs ~/rpmbuild/SPECS/httpd.spec

$ cat << EOF > ~/.rpmmacros
%_topdir %(echo $HOME)/rpmbuild
%_builddir %{_topdir}/BUILD
%_rpmdir %{_topdir}/RPMS
%_sourcedir %{_topdir}/SOURCES
%_specdir %{_topdir}/SPECS
%_srcrpmdir %{_topdir}/SRPMS
%debug_package %{nil}
EOF

$ rpmbuild -ba ~/rpmbuild/SPECS/httpd.spec

$ cd ~/rpmbuild/RPMS/x86_64/

$ rpm2cpio httpd-2.2.15-28.sl6.x86_64.rpm | cpio -id

 

~/rpmbuild/SPECS/httpd.specとして保存するファイルはここに置いています。
変更箇所は以下です。

$ diff ~/rpmbuild/SPECS/httpd.spec{,.org}
105,108d104
<
< # my patch
< Patch212: httpd-2.2.15-mod_expires.patch
<
261,263d256
< # my patch
< %patch212 -p1 -b .mod_expires
<

 

モジュールのコピーはrootかsudoで行なってください。

# cp -i ~rpmbuilder/rpmbuild/RPMS/x86_64/usr/lib64/httpd/modules/mod_expires.so /usr/lib64/httpd/modules/mod_expires.so

 

あとは、httpd.confに以下のような設定をすればOKです。
この例では、画像のみ1ヶ月キャッシュして、
それ以外はすべてキャッシュしないようにしています。

SetEnv override_expires 1

<IfModule mod_expires.c>
    ExpiresActive on
    ExpiresByType image/png "access plus 1 months"
    ExpiresByType image/jpeg "access plus 1 months"
    ExpiresByType image/jpg "access plus 1 months"
    ExpiresByType image/gif "access plus 1 months"
    ExpiresDefault A0
</IfModule>

 

以下のブログエントリを参考にさせて頂きました。
ありがとうございますm(_ _)m

@kazeburoさん 
mod_expiresでExpiresとCache-Controlを上書きする
mod_expiresのパッチファイルは@kazeburoさんのそのまま使わせて頂いてます><

@tnmtさん 
いまさら聞けないrpmbuildことはじめ


コメントする

nginxをリバースプロキシにしてGlassFishでクラスタリング

GlassFishクラスタ構成を作った時のメモです。

バージョンは、
OS: Scientific Linux 6.3 x86_64
nginx: 1.2.6
GlassFish: 3.1.2.2
Java: Java SE 7u11
いずれも今日時点の最新安定バージョンです。

アプリはJavaEE6で作ってます。
ステートフルなアプリなので、スティッキーセッションとセッションレプリケーションが必要です。

 

構成はこんな感じ。
DAS(ドメイン管理サーバ)がクラスタを管理します。
# 今回はGlassFishクラスタの話なので、「nginxとDASの冗長化は?」というツッコミは無しでお願いします:)

GlassFishのレプリケーション方式はインメモリレプリケーションです。
# 昔のGlassFishではHigh-Availability Database (HADB)という方式があったようですが、
# もう無くなったみたいですね。

レプリケーションについてはこのページがわかりやすいです。
Clustering in GlassFish Version 3.1

では、クラスタ構成の手順です。
このブログエントリーを参考にさせて頂きました。ありがとうございます!
Glassfish 3.1の自己増殖クラスタを試す

1. ノード準備
※この手順だけはDASではなく、各ノードで行います。

  • glassfishユーザ作成(GlassFishはglassfishユーザで実行する事にします)
  • GlassFishインストールディレクトリ作成
  • GlassFishインストールディレクトリのオーナーをglassfishに変更
  • Javaインストール
groupadd glassfish
useradd -g glassfish glassfish
mkdir /usr/local/glassfish3
chown glassfish:glassfish /usr/local/glassfish3
rpm -ivh /path/to/JDKのrpmファイル

2. クラスタ作成

asadmin create-cluster cluster1

※EC2環境で稼働させる場合、マルチキャストが使えないため以下のように明示的にノードを列挙してください。
詳しくはこのページを参照。
Discovering a Cluster When Multicast Transport Is Unavailable
@yoshioteradaさんに教えて頂きました。ありがとうございます!

※同一ノードにインスタンスを複数立ててクラスタを複数作る場合は、ポートが被らないように注意しましょう。

asadmin create-cluster --properties GMS_DISCOVERY_URI_LIST=tcp'\\:'//glassfish01,tcp'\\:'//glassfish02,tcp'\\:'//glassfish03:GMS_LISTENER_PORT=9090 cluster1

3. DASからノードへのssh接続設定

echo "AS_ADMIN_SSHPASSWORD=ノードのglassfishユーザのパスワード" > /tmp/password
asadmin --passwordfile=/tmp/password --interactive=false setup-ssh --sshuser glassfish --generatekey=true glassfish01
asadmin --passwordfile=/tmp/password --interactive=false setup-ssh --sshuser glassfish --generatekey=true glassfish02
asadmin --passwordfile=/tmp/password --interactive=false setup-ssh --sshuser glassfish --generatekey=true glassfish03
rm /tmp/password

4. DASからsshでノードのjavaコマンドが使えるか確認

ssh glassfish01 java -version
ssh glassfish02 java -version
ssh glassfish03 java -version

5. ノードにGlassFishをインストール

asadmin install-node --installdir /usr/local/glassfish3 --sshuser glassfish --sshkeyfile ~/.ssh/id_rsa glassfish01
asadmin install-node --installdir /usr/local/glassfish3 --sshuser glassfish --sshkeyfile ~/.ssh/id_rsa glassfish02
asadmin install-node --installdir /usr/local/glassfish3 --sshuser glassfish --sshkeyfile ~/.ssh/id_rsa glassfish03

6. DASにノードを追加

asadmin create-node-ssh --nodehost glassfish01 --sshuser glassfish --sshkeyfile ~/.ssh/id_rsa --installdir /usr/local/glassfish3 glassfish01
asadmin create-node-ssh --nodehost glassfish02 --sshuser glassfish --sshkeyfile ~/.ssh/id_rsa --installdir /usr/local/glassfish3 glassfish02
asadmin create-node-ssh --nodehost glassfish03 --sshuser glassfish --sshkeyfile ~/.ssh/id_rsa --installdir /usr/local/glassfish3 glassfish03

7. ノードにインスタンスを追加

asadmin create-instance --node glassfish01 --cluster cluster1 instance1
asadmin create-instance --node glassfish02 --cluster cluster1 instance2
asadmin create-instance --node glassfish03 --cluster cluster1 instance3

8. スティッキーセッションのためのプロパティ設定
この設定を行うと、Cookieの末尾にインスタンス識別子が付加され、
リクエストが振り分けられるインスタンスが固定されます。

asadmin create-system-properties --target instance1 INSTANCE=i1
asadmin create-system-properties --target instance2 INSTANCE=i2
asadmin create-system-properties --target instance3 INSTANCE=i3
asadmin create-jvm-options --target cluster1 -DjvmRoute=\${INSTANCE}

9. クラスタを起動
※OS起動時のクラスタの自動起動がわかりませんでした。ご存知の方教えて下さい><

asadmin start-cluster cluster1

GlassFishの設定はここまで。
次はnginxのインストールと設定です。

nginxは標準でスティッキーセッションの機能がありませんが、
モジュールを作ってくれている方がいらっしゃるので、ありがたく使わせて頂きます。

1. nginxインストール

ソースからインストールするので、ユーザを作ります。

groupadd nginx
useradd -g nginx nginx

依存ライブラリのインストール

yum install pcre-devel

ソースと追加モジュールのダウンロード

cd /usr/local/src
wget http://nginx.org/download/nginx-1.2.6.tar.gz
svn checkout http://nginx-sticky-module.googlecode.com/svn/trunk/ nginx-sticky-module-read-only

インストール
※各種パスとモジュールの指定は適宜読み替えてください。

tar xvf nginx-1.2.6.tar.gz
cd nginx-1.2.6
./configure \
  --prefix=/var/www \
  --user=nginx \
  --group=nginx \
  --sbin-path=/usr/local/sbin/nginx \
  --conf-path=/etc/nginx/nginx.conf \
  --error-log-path=/var/log/nginx/error.log \
  --http-log-path=/var/log/nginx/access.log \
  --pid-path=/var/run/nginx/nginx.pid \
  --lock-path=/var/lock/subsys/nginx.lock \
  --with-http_ssl_module \
  --with-http_realip_module \
  --with-http_gzip_static_module \
  --with-http_stub_status_module \
  --add-module=../nginx-sticky-module-read-only
make
make install

2. 設定ファイル修正
nginxのconfigureの時に–conf-pathで指定したパスに設定ファイルが出来ています。
以下、設定例です。
※スティッキーセッションに必要な設定は、upstreamのstickyだけです。

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log debug;

pid        /var/run/nginx/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" '
                      '$upstream_response_time';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;

    tcp_nopush      off;

    keepalive_timeout  65;

    gzip  off;

    server_tokens off;

    upstream cluster1 {
        sticky;
        server glassfish01:28080 max_fails=2 fail_timeout=10m;
        server glassfish02:28080 max_fails=2 fail_timeout=10m;
        server glassfish03:28080 max_fails=2 fail_timeout=10m;
    }

    server {
        listen       80;

        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;

        error_page  404              /404.html;
        error_page  500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        location / {
            root   /var/www/html;
            index  index.html index.htm;
        }

        location /favicon.ico {
            root   /var/www/html;
        }

        location /warファイルコンテキスト名/ {
            proxy_pass    http://cluster1;
        }
    }
}

以上で環境は出来ました。
動作確認してみます。

1. 確認用ソース
以下をsession.jspという名前で保存して、warファイルを作成します。

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="java.net.InetAddress" %>
<%@ page import="java.text.SimpleDateFormat" %>
<%@ page import="java.util.Date" %>
<%@ page import="javax.servlet.http.Cookie" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style type="text/css">
<!--
body
{
	font-family: 'MS ゴシック';
	font-size: 9pt;
}

table
{
	border-collapse: collapse;
	border-left: 1px;
	border-right: 1px;
	border-top: 1px;
	border-bottom: 1px;
	border-style: solid;
	border-color: #000000;
}

table.border
{
	border-left: 0px;
	border-right: 0px;
	border-top: medium solid;
	border-bottom: 0px;
	border-color: #000000;
}

td
{
	border-left: 0px;
	border-right: 1px;
	border-top: 0px;
	border-bottom: 1px;
	border-style: solid;
	border-color: #000000;
	margin: 2px;
	padding: 2px;
}

td.title-left
{
	background-color: #a3bacd;
	font-weight: bold;
	text-align: left;
}

td.title-center
{
	background-color: #a3bacd;
	font-weight: bold;
	text-align: center;
}
-->
</style>
</head>
<body>
<h1><font color="red">Session Info</font></h1>
<table align="center" border="0" width="800">
	<tr>
		<td class="title-left" width="120">Server</td>
		<td><%= InetAddress.getLocalHost().getHostName() %></td>
	</tr>
	<tr>
		<td class="title-left" width="120">Session ID</td>
		<td><%= session.getId() %></td>
	</tr>
	<tr>
		<td class="title-left" width="120">Created on</td>
		<td>
<%
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS");
			out.print(sdf.format(new Date(session.getCreationTime())));
%>
		</td>
	</tr>
	<tr>
		<td class="title-left" width="120">Request Count</td>
		<td>
<%
			if (session.getAttribute("count") == null) {
				session.setAttribute("count", new Integer(0));
			}
			int count = ((Integer)session.getAttribute("count")).intValue() + 1;
			session.setAttribute("count", new Integer(count));
			out.print(count);
%>
		</td>
	</tr>
</table>

<br/>
<table border="0" width="100%" class="border">
	<tr>
		<td width="80%"></td>
	</tr>
</table>

<h1><font color="red">Cookie Info</font></h1>
<table align="center" border="0" width="800">
	<tr>
		<td class="title-center">Name</td>
		<td class="title-center">Value</td>
		<td class="title-center">Domain</td>
		<td class="title-center">Path</td>
		<td class="title-center">MaxAge</td>
		<td class="title-center">Comment</td>
		<td class="title-center">Secure</td>
		<td class="title-center">Version</td>
	</tr>
<%
	Cookie[] cookies=request.getCookies();
	if (cookies != null) {
		for (int i=0; i<cookies.length; i++) {
			Cookie cookie = cookies[i];
			out.print("<tr>"  + "\r\n");
			out.print("<td>"  + cookie.getName()    + "</td>" + "\r\n");
			out.print("<td>"  + cookie.getValue()   + "</td>" + "\r\n");
			out.print("<td>"  + cookie.getDomain()  + "</td>" + "\r\n");
			out.print("<td>"  + cookie.getPath()    + "</td>" + "\r\n");
			out.print("<td>"  + cookie.getMaxAge()  + "</td>" + "\r\n");
			out.print("<td>"  + cookie.getComment() + "</td>" + "\r\n");
			out.print("<td>"  + cookie.getSecure()  + "</td>" + "\r\n");
			out.print("<td>"  + cookie.getVersion() + "</td>" + "\r\n");
			out.print("</tr>" + "\r\n");
		}
	}
%>
</table>

</body>
</html>

2. デプロイ
Jenkinsを使ってビルドからデプロイまで自動化するのが良いと思います。

asadmin --host=glassfish00 --port=4848 --passwordfile=/path/to/.glassfishpasswd deploy --target=cluster1 --contextroot=warファイルコンテキスト名 --force=true --property java-web-start-enabled=false:preserveAppScopedResources=true /path/to/warファイル

3. ブラウザで確認
http://nginxホスト名/warファイルコンテキスト名/session.jsp
を表示して、リクエストの度に「Request Count」の値がカウントアップされる事、
「JSESSIONID」の値が変わらない事を確認します。

4. GlassFishのWeb管理コンソールから、テスト中のセッションを処理しているノードを停止します。

5. もう一度ブラウザで確認
http://nginxホスト名/warファイルコンテキスト名/session.jsp
を再度表示して、「Request Count」の値が初期化されずにカウントアップされる事、
「JSESSIONID」の値はインスタンス識別子の部分だけ変わる事を確認します。

以上で完了です。

最後に
nginx + GlassFishの組み合わせは情報が少なかったです。
昔からApacheを使うのが当たり前ではありますが、(マニュアルにも載ってるし)
個人的には、nginxの方が設定がわかりやすくパフォーマンスも優れていると思います。


コメントする

NetBeansでJavaEE6開発する時にHotDeploy可能なプロジェクトタイプ

これはJavaEE Advent Calendar 2012の11日目のエントリです。

昨日は@mike_neckさんの「CDIについて書こうとしたら、JSRを読み始めて、結果JSR330を実装してみようとしてみた件について」でした。

弊社ではNetBeans&GlassFishを使っています。
今回のエントリーでは、JavaEE6初心者な僕が最初に困った点について書こうと思います。
これから始める方の、何かのお役に立てれば!

# 業務で使う標準フレームワークを最近JavaEE6に切り替えたばかりです。
# ちなみに、その前はSeasar2(S2Dao + Teeda)&Eclipse&Tomcatでした。

NetBeansでJavaEE6なWebアプリを開発する場合、プロジェクトのタイプは以下の4つが選べます。

  1. NetBeans独自形式プロジェクト + warパッケージング
  2. NetBeans独自形式プロジェクト + earパッケージング
  3. Maven形式プロジェクト + warパッケージング
  4. Maven形式プロジェクト + earパッケージング

 

JavaEE6になってから、ejbもwarファイルにパッケージングが出来るようになったのは
皆さん既にご存じかと思います。

 

各プロジェクト作成ウィザードのスクリーンショットはこちら。
 

1. NetBeans独自形式プロジェクト + warパッケージング
スクリーンショット 2012-12-11 15.29.26

2. NetBeans独自形式プロジェクト + earパッケージング
スクリーンショット 2012-12-11 15.29.41

3. Maven形式プロジェクト + warパッケージング
スクリーンショット 2012-12-11 15.33.45

4. Maven形式プロジェクト + earパッケージング
スクリーンショット 2012-12-11 15.34.01

弊社ではMaven形式プロジェクト + earパッケージングで開発を始めました。
理由としては、開発するサービスが非常に大きいからです。
クラス数が多いとAPサーバの起動に時間が掛かるし、メモリも消費するので、
ある程度の機能単位でejb(jar)に分けて、導入するお客様の環境で必要な分だけパッケージングしようと考えました。

こんな感じです。

  • A社様向け → 機能1 + 機能2 + 機能3
  • B社様向け → 機能2
  • C社様向け → 機能1 + 機能3

ですが、earでパッケージングしてしまうと、開発が始まって非常に困った問題が発覚。
ソースの変更を行った場合、変更を反映するために再ビルド&GlassFish再起動が必要という状況(ToT)
これでは開発の効率が非常に悪くなってしまいます。

そこで、warファイルにパッケージングしてみたところ、ソースを変更して保存したタイミングで、
アプリのHotDeploy(と呼んでいいのかな?)が行われました。

プロジェクトタイプごとのHotDeployの挙動をまとめるとこんな感じです。

  1. NetBeans独自形式プロジェクト + warパッケージング → HotDeploy可能
  2. NetBeans独自形式プロジェクト + earパッケージング → HotDeploy不可能
  3. Maven形式プロジェクト + warパッケージング → HotDeploy可能
  4. Maven形式プロジェクト + earパッケージング → HotDeploy不可能

 

GlassFishの管理コンソールでアプリケーションがデプロイされたパスを確認して、
そのディレクトリの中身を見てみると、
HotDeploy可能な場合はclassファイルがフラットな状態で配置されていて、
不可能なプロジェクトタイプではjarとかwarにパッケージングされて配置されてました。

1. NetBeans独自形式プロジェクト + warパッケージング
スクリーンショット 2012-12-11 22.29.53

2. NetBeans独自形式プロジェクト + earパッケージング
スクリーンショット 2012-12-11 22.30.09

3. Maven形式プロジェクト + warパッケージング
スクリーンショット 2012-12-11 22.31.18

4. Maven形式プロジェクト + earパッケージング
スクリーンショット 2012-12-11 22.30.34
 

NetBeans独自形式プロジェクトのビルドツールはAntみたいですね。
せっかく新しく始めるんだし、もうAntは使いたくないので、
Maven形式プロジェクト + warパッケージングでやる事にしました。
(今時はGradleなのでしょうか?)
サーバのメモリ消費問題は、warにパッケージングする時に不要な分は除外する方法で対応するつもりです。

規模が大きい開発の場合、みなさんどうされているのでしょうか。。。(ERPとか)
「earパッケージングでもHotDeploy出来るよ!」というツッコミ等あれば是非お願いしますm(_ _)m

明日(2012/12/12)は@kikutaro_さんです。
よろしくお願いします!!


2件のコメント

CDIで複数データソースの切り替え

将来的にはJavaEEの仕様にマルチテナント機能が組み込まれますが、
僕は今のところ、複数データソースの切り替えが出来れば十分なので、
CDIを使ってやってみました。
これで1つのJavaEE6アプリで複数のデータベースへの接続を切り替える事が可能です。

では早速、実際のソースを見てみましょう。

 

まずはプロデューサ・メソッドから。
EntityManagerがDIされるフィードを複数持ち、プロデューサ・メソッドで何れかをreturnします。
今回の例ではシステム日付の秒で分岐しましたが、
実際はURLパラメータやCookieに保持した値などで分岐する事になると思います。
また、RequestScopedアノテーションを付けて、リクエスト毎に呼ばれるようにします。

public class EntityManagerFactory {

    @PersistenceContext(unitName = "pu01")
    private EntityManager em01;
    @PersistenceContext(unitName = "pu02")
    private EntityManager em02;

    @RequestScoped
    @Produces
    @EntityManagerQualifier
    public EntityManager getEntityManager() {
        // システム日付が偶数秒ならem01に接続する。
        DateTime dt = new DateTime();
        int sec = Integer.parseInt(dt.toString("s"));
        if (sec % 2 == 0) {
            return em01;
        } else {
            return em02;
        }
    }
}

次にDIでEntityManagerを受け取る側
DIで受け取ったEntityManagerを使うだけです。
今回の例ではコントローラに書いてます。

@Model
public class SampleController {

    @Inject
    @EntityManagerQualifier
    private EntityManager em;

    public String getName() {
        Query query = em.createNamedQuery("findAll", Sample.class);
        return ((Sample) query.getSingleResult()).getName();
    }
}

EntityManagerQualifierアノテーションはQualifier(限定子)です。
インジェクションの対象を限定(紐付け?)させています。

@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface EntityManagerQualifier {
}

ソースはGitHubにアップしてます。
READMEに環境構築の手順メモも書いてます。
サンプルソース

以上、久々のJavaネタでした!


コメントする

Mac OS XのNetBeansとGlassFishの文字化け対策

同じ事何回もググってるのでいい加減まとめる。

MacのJVMのデフォルト文字コードはSJISなので、
NetBeansのコンソール、NetBeansのGlassFishコンソール、GlassFishのログファイルの内容が
文字化けして開発しにくいです。

# Java7でもSJISがデフォルトのままなのはナゼ・・・?

解決方法は以下のとおり。
※パスは自分の環境に合わせて読み替えてください。

・NetBeansのコンソール文字化け対策
/Applications/NetBeans/NetBeans 7.2.app/Contents/Resources/NetBeans/etc/netbeans.conf
のnetbeans_default_optionsに以下を追加

-J-Dfile.encoding=UTF-8

 

・NetBeansのGlassFishコンソール、GlassFishのログファイル文字化け対策
/Applications/NetBeans/glassfish-3.1.2.2/glassfish/domains/domain1/config/domain.xml
server-configのjava-configの中に以下を追加

-Dfile.encoding=UTF-8

ちなみに、ログファイルのパスはこれ。
/Applications/NetBeans/glassfish-3.1.2.2/glassfish/domains/domain1/logs/server.log


コメントする

第3回チューニンガソンに参加して来ました

 

先日行われた「第3回チューニンガソン(Tuningathon)」に参加して来ました。

僕は福岡在住なので、福岡サテライトでの参加です。

Zusaar 福岡サテライト

Zusaar 東京本会場

今回はblojsom + Tomcat + MySQLでした。

結果は5位。
仕事でTomcatを5年くらい運用管理している現役Java屋なので、
優勝しないといけない気がしますが。。。

福岡会場はJavaやった事がない方がほとんどで、
最近はホントにJavaって少数派なんだと実感しました。
なんか寂しい(ToT)
東京本会場はどうだったのかな?

条件は、

  • Tomcatをフロントにして、DBはMySQLにすること。
  • Tomcat, Java, MySQLの入れ替えはOK。
  • webapps/blojsom以下は改変NG。アプリ配置パスも変更不可。

という感じです。

僕がやった内容は以下になります。

  • JVMをJRockitに
  • JVMメモリ割り当てを調整
  • Tomcatを7に
  • Tomcatのserver.xmlのパラメータ調整
  • webapps以下はblojsomとmanager(Tomcatの管理コンソール)以外削除

自分の復習も兼ねて、チューニング内容などをまとめておきます。

■JVMをJRockitに
JRockit(ジェイロキット)とはBEA Systemsが開発したJVMです。
BEAはOracleに買収されているので、現在はOracleの製品になってます。
この記事がわかりやすいです。

# BEA懐かしい。。。初めてJavaやった時のサーバはWebLogicでした。

仕事で使った事はなかったのですが、昔から早いと評判だったので使ってみました。

JRockitのJVMパラメータはOracleJVM(HotSpot)とちょっと違います。

OracleJVM(HotSpot)のパラメータの一部
JRockitのJVMパラメータ

# SunがOracleに買収されてからJavaのページ構成が変わったので、
# OracleJVM(HotSpot)の全パラメータの説明が載ってる公式ドキュメントが探しきれなくなってる。

■JVMメモリ割り当てを調整
指定したパラメータは、
-Xms:1408m
-Xmx:1408m
の2つだけです。
※ヒープ領域拡張のオーバーヘッドを防ぐため、-Xmsと-Xmxは同じ値にしましょう。

JRockitだとパラメータがだいぶ違うみたいで、OracleJVM(HotSpot)でいつも使っている
パラメータを設定すると起動すらしませんでした。
今回は時間も限られているので、JRockitのドキュメントを読むのはやめました。

いつもは、以下のパラメータあたりを設定してます。
※OracleJVM(HotSpot)の話です。JRockitでは違うかも。

JVMには大きく分けて3つのメモリ領域が存在します。
1. ヒープ領域
2. パーマネント領域
3. スレッドスタック領域

ヒープ領域の内部は更にNew領域、Old領域の2つに分かれます。
New領域の内部は更にEden領域、From領域、To領域の3つに分かれます。

この記事と、この記事がわかりやすいです。

・ヒープ領域の設定項目の代表的なもの
※他にもあります。
-Xms
-Xmx
-XX:NewSize
-XX:MaxNewSize
-XX:NewRatio
-XX:SurvivorRatio
-XX:TargetSurvivorRatio

・パーマネント領域の設定項目
-XX:PermSize
-XX:MaxPermSize

・スレッドスタック領域の設定項目
-Xss

今回は大きなアプリではないので、パーマネント領域、スレッドスタック領域は調整不要だったと思います。
-serverを指定した方がいらっしゃいましたが、Java6からはserverがデフォルトだった気がします。
公式ドキュメントは見つけられませんでした。。。

ヒープ領域の状態をグラフにするGCViwerというツールがあります。
-Xloggc
-XX:+PrintGCDetails
あたりのパラメータを指定します。

JVMパラメータはコマンドライン引数なので、
設定したパラメータがちゃんと反映されているかどうかは、psコマンドで確認できます。

こんな感じです。

tomcat   22113     1  0 Mar23 ?        00:04:12 /usr/java/default/bin/../bin/java -Xms1408m -Xmx1408m (長いので以降省略)

上記以外でパフォーマンスチューニングでやる事といえば、
プロファイラ、ヒープダンプ、スレッドダンプあたりでしょうか。
今回はやってません。。。

■Tomcatを7に
Tomcatはyumでインストールされてました。
yumでインストールした場合とアーカイブからインストールした場合で
フォルダやファイルの構成がちょっと違います。
以下のような感じです。

☆yumでインストールした場合
・インストールディレクトリ
/usr/share/tomcat6

・JVMパラメータ設定ファイルと変数
/usr/share/tomcat6/conf/tomcat6.conf
JAVA_OPTS変数

・Tomcatログファイル
/usr/share/tomcat6/logs/catalina.out

中身はこんな感じです。

$ ls -l /usr/share/tomcat6
合計 4
drwxr-xr-x. 2 root root   4096  3月 25 01:22 2012 bin
lrwxrwxrwx. 1 root tomcat   12  3月 25 01:22 2012 conf -> /etc/tomcat6
lrwxrwxrwx. 1 root root     23  3月 25 01:22 2012 lib -> /usr/share/java/tomcat6
lrwxrwxrwx. 1 root root     16  3月 25 01:22 2012 logs -> /var/log/tomcat6
lrwxrwxrwx. 1 root root     23  3月 25 01:22 2012 temp -> /var/cache/tomcat6/temp
lrwxrwxrwx. 1 root root     24  3月 25 01:22 2012 webapps -> /var/lib/tomcat6/webapps
lrwxrwxrwx. 1 root root     23  3月 25 01:22 2012 work -> /var/cache/tomcat6/work

シンボリックリンクでOS標準のディレクトリに配置されてます。
RHEL系はCATALINA_BASEとCATALINA_HOMEが同じディレクトリなので、
「/usr/share/tomcat6」以下にまとまってます。

Debian系はCATALINA_BASEとCATALINA_HOMEが別になっているので、
もうちょっと散らばっています。

☆アーカイブでインストールした場合
・インストールディレクトリ
任意

・JVMパラメータ設定ファイルと変数
以下のどちらか。
インストールディレクトリ/bin/catalina.sh
インストールディレクトリ/bin/setenv.sh
CATALINA_OPTS変数またはJAVA_OPTS変数
※setenv.shはアーカイブには含まれていません。
ファイルを作成すればcatalina.shから読み込まれます。

・Tomcatログファイル
インストールディレクトリ/logs/catalina.out

・init.dに配置する起動スクリプト
アーカイブでインストールする場合は自分で準備します。
今回はここを参考にさせてもらいました。

中身はこんな感じです。

$ ls -l
合計 112
-rw-r--r--. 1 root root 56797  2月 17 23:13 2012 LICENSE
-rw-r--r--. 1 root root  1192  2月 17 23:13 2012 NOTICE
-rw-r--r--. 1 root root  8826  2月 17 23:13 2012 RELEASE-NOTES
-rw-r--r--. 1 root root 10597  2月 17 23:13 2012 RUNNING.txt
drwxr-xr-x. 2 root root  4096  3月 25 02:34 2012 bin
drwxr-xr-x. 2 root root  4096  2月 17 23:13 2012 conf
drwxr-xr-x. 2 root root  4096  3月 25 02:34 2012 lib
drwxr-xr-x. 2 root root  4096  2月 17 23:11 2012 logs
drwxr-xr-x. 2 root root  4096  3月 25 02:34 2012 temp
drwxr-xr-x. 7 root root  4096  2月 17 23:13 2012 webapps
drwxr-xr-x. 2 root root  4096  2月 17 23:11 2012 work

■Tomcatのserver.xmlのパラメータ調整
enableLookups=”false”
compressableMimeTypes=”text/html,text/xml,text/plain,text/css,text/javascript”
compression=”on”
maxKeepAliveRequests=”2000″
HTTPコネクタのパラメータ

この辺のパラメータは初めて設定しました。
実運用だとフロントにApacheとかを立ててAJPで通信するので、
Tomcatでは設定しないパラメータだと思います。

■webapps以下はblojsomとmanager(Tomcatの管理コンソール)以外削除
使っていないアプリは削除しないとメモリをムダに消費します。
blojsomとmanager以外は削除しました。

■その他チャレンジした事
・/etc/my.cnfを調整
MySQLをまともに触ったのが初めてだったので、色んなパラメータを適当に大きくしてみました。
素人がやっても効果は無かったです(ToT)
・MySQLを5.5に
rpmを取ってきてインストールした後、5.1の環境からダンプでデータを入れてみました。
mysqlクライアントからは繋がったのですが、アプリから繋がらなくなったので、元に戻しました。
原因不明です(ToT)

最後にスコアの推移を載せておきます。
スコアにバラつきが多い事に気づいたのがかなり遅かったので、
効果が無かったチューニングも多かったと思います。

■初期状態

[root@ip-10-154-31-26 3:18:09]# /home/ec2-user/bench.sh
166 fetches, 9 max parallel, 2.2654e+06 bytes, in 10 seconds
13647 mean bytes/connection
16.6 fetches/sec, 226540 bytes/sec
msecs/connect: 0.144114 mean, 0.427 max, 0.047 min
msecs/first-response: 522.831 mean, 1664.53 max, 118.706 min
HTTP response codes:
  code 200 -- 166
Score: 17.532 (get=16.600, comment=0.932(1), check=1.000)
Score sending was succeeded.

■JRockitにしてパラメータ調整した後

[root@ip-10-154-31-26 4:42:46]# /home/ec2-user/bench.sh
452 fetches, 9 max parallel, 7.37516e+06 bytes, in 10 seconds
16316.7 mean bytes/connection
45.1998 fetches/sec, 737513 bytes/sec
msecs/connect: 0.421542 mean, 61.588 max, 0.093 min
msecs/first-response: 187.649 mean, 662.327 max, 75.404 min
451 bad byte counts
HTTP response codes:
  code 200 -- 452
Score: 50.018 (get=45.200, comment=4.818(6), check=1.000)
Score sending was succeeded.

■Tomcat7に変更した後

[root@ip-10-154-31-26 5:52:18]# /home/ec2-user/bench.sh
541 fetches, 9 max parallel, 7.38303e+06 bytes, in 10 seconds
13647 mean bytes/connection
54.1 fetches/sec, 738303 bytes/sec
msecs/connect: 0.153067 mean, 1 max, 0.043 min
msecs/first-response: 156.761 mean, 550.082 max, 77.279 min
HTTP response codes:
  code 200 -- 541
Score: 55.083 (get=54.100, comment=0.983(1), check=1.000)
Score sending was succeeded.

■server.xmlを調整した後

[root@ip-10-154-31-26 7:39:09]# /home/ec2-user/bench.sh
467 fetches, 9 max parallel, 7.2862e+06 bytes, in 10 seconds
15602.1 mean bytes/connection
46.7 fetches/sec, 728620 bytes/sec
msecs/connect: 0.492951 mean, 91.456 max, 0.042 min
msecs/first-response: 183.904 mean, 958.097 max, 66.788 min
252 bad byte counts
HTTP response codes:
  code 200 -- 467
Score: 58.690 (get=46.700, comment=11.990(12), check=1.000)
Score sending was succeeded.

最後の計測の時は55秒台だったと思います。

MySQLとOS周りをもっと勉強して、次回また頑張ります!

■3/27追記
JITコンパイルについても理解しておいた方がいいと思うので、
わかりやすい記事へのリンクを追加しておきます。
Hotspot VMの基本構造を理解する