ねこ踏んじゃった系エントリ:Tomcatいじめたら意外と強かったこと #javaee

これは Java EE Advent Calendar 2014 の6日目です。昨日は tq_jappy さんの「Java SE 8とJava EE 7によるアプリケーションのモダナイゼーション~中間ふりかえり~」、明日は yamadamn さんです。

ServletだってJava EE、だったらTomcatだって。。。

Java EEと言えばWildFlyかGlassfishかあるいは商用アプリケーションサーバーで、CDIとかEJBとかJSF....を使うかっこいいやつを言うらしいです。。。でも僕はまだ使ったことがありません。

僕はまだTomcatを使っています。
Tomcatといえば「可愛くないねこ」
_人人人人人人人人人_
> 可愛くないねこ <
 ̄Y^Y^Y^Y^Y^Y^Y^Y ̄
「もっともかわいくないねこ」と言われる公式ロゴ
など、散々な言われ方をしています。。そんないじめなくてもいいのに!たしかに拡張性がありそうで意外とそうでもなかったり、ソース読むと袋小路に陥るなど、開発者に服従しないあたり、そう、とてもねこっぽい。挙動萌えじゃないですか。Mな僕は満足です!
※このエントリの筆者はねこを飼ったことがないので正しくないかもしれませんのでご容赦ください

Java EEのカレンダーにTomcatってどうなのかってことですが、ほら、Servetだって立派なJava EEの一部です。そして2日目のirofさんが

全部を使わなきゃいけないわけじゃありません。

っておっしゃってるじゃないですか!(Java EEを説明してみる #javaee - 2014-12-02 - 日々常々

Java EE全部使おうとしたら実はServletとJSPくらいで良かった、だから僕はJava EEを使っているのであって、TomcatがJava EEアドベントカレンダーに出てきても許してにゃん!

2014年の僕の中でのトピックは、Tomcatの戦闘力の高さ

今年の個人的なトピックは、わけあってTomcatの底力を思い知ったことでした。半端ないです。いじめて反撃食らった心境です。童謡にもあるでしょ踏んだらひっかくって!

ということで、試しに踏んでみたいと思います。

実験概要

この実験では、大量のwebアプリケーションを作ります。Webアプリケーションはこんな簡単なServletだけでいいです。

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/plain");
        response.setCharacterEncoding("UTF-8");
        PrintWriter writer = response.getWriter();
        writer.println("こんにちは世界");
    }
}

これをなんとかしてHello.warというwarファイルにしたとします。次にこんなスクリプトを書いて、大量生成します

> jrunscript -e "for(var i=0;i<1000;i++)cp('Hello.war','Hello' + i + '.war');"

実験:1000個のWebアプリケーションを一気にデプロイするのに必要な時間と消費メモリ

Tomcatはwarファイルをwebappsディレクトリ以下に配置すれば自動的にWebアプリケーションとしてロードします。なので大量に作ったwarファイルを一気にwebappsに移動してみましょう。

その時のログが以下の通り。

情報: Webアプリケーションアーカイブ H:\java\apache-tomcat-7.0.54\webapps\Hello992.war を配備します
12 04, 2014 12:07:33 午前 org.apache.catalina.startup.HostConfig deployWAR
情報: Deployment of web application archive H:\java\apache-tomcat-7.0.54\webapps\Hello992.war has finished in 47 ms
12 04, 2014 12:07:33 午前 org.apache.catalina.startup.HostConfig deployWAR
情報: Webアプリケーションアーカイブ H:\java\apache-tomcat-7.0.54\webapps\Hello993.war を配備します
12 04, 2014 12:07:33 午前 org.apache.catalina.startup.HostConfig deployWAR
情報: Deployment of web application archive H:\java\apache-tomcat-7.0.54\webapps\Hello993.war has finished in 31 ms
12 04, 2014 12:07:33 午前 org.apache.catalina.startup.HostConfig deployWAR
情報: Webアプリケーションアーカイブ H:\java\apache-tomcat-7.0.54\webapps\Hello994.war を配備します
12 04, 2014 12:07:33 午前 org.apache.catalina.startup.HostConfig deployWAR
情報: Deployment of web application archive H:\java\apache-tomcat-7.0.54\webapps\Hello994.war has finished in 31 ms
12 04, 2014 12:07:33 午前 org.apache.catalina.startup.HostConfig deployWAR
情報: Webアプリケーションアーカイブ H:\java\apache-tomcat-7.0.54\webapps\Hello995.war を配備します
12 04, 2014 12:07:33 午前 org.apache.catalina.startup.HostConfig deployWAR
情報: Deployment of web application archive H:\java\apache-tomcat-7.0.54\webapps\Hello995.war has finished in 31 ms
12 04, 2014 12:07:33 午前 org.apache.catalina.startup.HostConfig deployWAR
情報: Webアプリケーションアーカイブ H:\java\apache-tomcat-7.0.54\webapps\Hello996.war を配備します
12 04, 2014 12:07:33 午前 org.apache.catalina.startup.HostConfig deployWAR
情報: Deployment of web application archive H:\java\apache-tomcat-7.0.54\webapps\Hello996.war has finished in 31 ms
12 04, 2014 12:07:33 午前 org.apache.catalina.startup.HostConfig deployWAR
情報: Webアプリケーションアーカイブ H:\java\apache-tomcat-7.0.54\webapps\Hello997.war を配備します
12 04, 2014 12:07:33 午前 org.apache.catalina.startup.HostConfig deployWAR
情報: Deployment of web application archive H:\java\apache-tomcat-7.0.54\webapps\Hello997.war has finished in 32 ms
12 04, 2014 12:07:33 午前 org.apache.catalina.startup.HostConfig deployWAR
情報: Webアプリケーションアーカイブ H:\java\apache-tomcat-7.0.54\webapps\Hello998.war を配備します
12 04, 2014 12:07:33 午前 org.apache.catalina.startup.HostConfig deployWAR
情報: Deployment of web application archive H:\java\apache-tomcat-7.0.54\webapps\Hello998.war has finished in 31 ms
12 04, 2014 12:07:33 午前 org.apache.catalina.startup.HostConfig deployWAR
情報: Webアプリケーションアーカイブ H:\java\apache-tomcat-7.0.54\webapps\Hello999.war を配備します
12 04, 2014 12:07:33 午前 org.apache.catalina.startup.HostConfig deployWAR
情報: Deployment of web application archive H:\java\apache-tomcat-7.0.54\webapps\Hello999.war has finished in 32 ms

こんな感じで粛々とロードします。(Tomcatの最新安定版は8.x系ですが、これは実験した時のバージョンと同じで7系にしています。8でも同じだと思うけど試してません)

1分以内に全部のロードが終わりました。
その際のリソース使用量はこんな感じです。

徐々に上がって、メモリ使用量750MBあたりで安定

本当にデプロイされているのか、全部のアプリケーションにアクセスしてみましょう。

> jrunscript -e "for(var i=0;i<1000;i++)cat('http://localhost:8080/Hello' + i + '/hello');"

ねこだけにcat関数!サクッと値返してます!

こんにちは世界
こんにちは世界
こんにちは世界
こんにちは世界
こんにちは世界

※Windowsのコマンドプロンプトだと、文字化けします。文字コードがShiftJISじゃないとダメみたいです。

管理コンソールだってちゃんと機能します。ほら!
アプリ1000個くらいデプロイしても管理画面はさくっと機能します

最後に、アンデプロイです。これは、warファイルを消すと、それを検知して勝手に配備解除してくれます。解除の順番はばらばらで、配置よりはゆっくりですが、でも確実に配備解除されました。おりこうですね!

情報: コンテキストパス /Hello465 のWebアプリケーションの配備を解除します
12 04, 2014 12:12:42 午前 org.apache.catalina.startup.HostConfig undeploy
情報: コンテキストパス /Hello924 のWebアプリケーションの配備を解除します
12 04, 2014 12:12:43 午前 org.apache.catalina.startup.HostConfig undeploy
情報: コンテキストパス /Hello332 のWebアプリケーションの配備を解除します
12 04, 2014 12:12:44 午前 org.apache.catalina.startup.HostConfig undeploy
情報: コンテキストパス /Hello152 のWebアプリケーションの配備を解除します
12 04, 2014 12:12:45 午前 org.apache.catalina.startup.HostConfig undeploy
情報: コンテキストパス /Hello179 のWebアプリケーションの配備を解除します
12 04, 2014 12:12:45 午前 org.apache.catalina.startup.HostConfig undeploy
情報: コンテキストパス /Hello194 のWebアプリケーションの配備を解除します
12 04, 2014 12:12:46 午前 org.apache.catalina.startup.HostConfig undeploy
情報: コンテキストパス /Hello528 のWebアプリケーションの配備を解除します
12 04, 2014 12:12:47 午前 org.apache.catalina.startup.HostConfig undeploy
情報: コンテキストパス /Hello370 のWebアプリケーションの配備を解除します

同じ実験をGlassfishでやってみる

同じ実験をGlassfishでやってみようと思います。結果を知ってるんで、やりたくないんですけどね。
Glassfishも同じように下記の場所にwarをコピーすると自動でデプロイされる仕組みがあります。

/glassfish/domains/ドメイン名/autodeploy

それでは、さっきの方法で。。。。warを大量コピーしてみます。

[#|2014-12-04T01:10:31.968+0900|INFO|glassfish 4.0|javax.enterprise.system.tools.deployment.common|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417623031968;_LevelValue=800;|
  visiting unvisited references|#]

[#|2014-12-04T01:10:31.984+0900|INFO|glassfish 4.0|javax.enterprise.system.tools.deployment.common|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417623031984;_LevelValue=800;|
  visiting unvisited references|#]

[#|2014-12-04T01:10:31.984+0900|INFO|glassfish 4.0|javax.enterprise.system.tools.deployment.common|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417623031984;_LevelValue=800;|
  visiting unvisited references|#]

[#|2014-12-04T01:10:32.031+0900|INFO|glassfish 4.0|javax.enterprise.web|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417623032031;_LevelValue=800;_MessageID=AS-WEB-GLUE-00172;|
  Loading application [Hello234] at [/Hello234]|#]

[#|2014-12-04T01:10:32.062+0900|INFO|glassfish 4.0|javax.enterprise.system.core|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417623032062;_LevelValue=800;|
  Hello234は、547ミリ秒で正常にデプロイされました。|#]

[#|2014-12-04T01:10:32.062+0900|INFO|glassfish 4.0|javax.enterprise.system.tools.deployment.autodeploy|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417623032062;_LevelValue=800;_MessageID=NCLS-
DEPLOYMENT-00035;|
  [AutoDeploy]自動デプロイは正常に実行されました : H:\java\glassfish4\glassfish\domains\domain1\autodeploy\Hello234.war。|#]

[#|2014-12-04T01:10:32.077+0900|INFO|glassfish 4.0|javax.enterprise.system.tools.deployment.autodeploy|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417623032077;_LevelValue=800;_MessageID=NCLS-
DEPLOYMENT-00027;|
  Selecting file H:\java\glassfish4\glassfish\domains\domain1\autodeploy\Hello805.war for autodeployment|#]

[#|2014-12-04T01:10:32.531+0900|INFO|glassfish 4.0|javax.enterprise.system.tools.deployment.common|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417623032531;_LevelValue=800;|
  visiting unvisited references|#]

[#|2014-12-04T01:10:32.546+0900|INFO|glassfish 4.0|javax.enterprise.system.tools.deployment.common|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417623032546;_LevelValue=800;|
  visiting unvisited references|#]

[#|2014-12-04T01:10:32.546+0900|INFO|glassfish 4.0|javax.enterprise.system.tools.deployment.common|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417623032546;_LevelValue=800;|
  visiting unvisited references|#]

[#|2014-12-04T01:10:32.609+0900|INFO|glassfish 4.0|javax.enterprise.web|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417623032609;_LevelValue=800;_MessageID=AS-WEB-GLUE-00172;|
  Loading application [Hello805] at [/Hello805]|#]

[#|2014-12-04T01:10:32.640+0900|INFO|glassfish 4.0|javax.enterprise.system.core|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417623032640;_LevelValue=800;|
  Hello805は、563ミリ秒で正常にデプロイされました。|#]

Tomcatのときは数10msで1アプリ起動していましたが、こちらはロード数が多いと徐々に遅くなっていきます。最終的には1アプリ7秒ほどの時間を費やしていました。

[#|2014-12-04T01:43:19.334+0900|INFO|glassfish 4.0|javax.enterprise.web|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417624999334;_LevelValue=800;_MessageID=AS-WEB-GLUE-00172;|
  Loading application [Hello200] at [/Hello200]|#]

[#|2014-12-04T01:43:19.422+0900|INFO|glassfish 4.0|javax.enterprise.system.core|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417624999422;_LevelValue=800;|
  Hello200は、7,828ミリ秒で正常にデプロイされました。|#]

[#|2014-12-04T01:43:19.424+0900|INFO|glassfish 4.0|javax.enterprise.system.tools.deployment.autodeploy|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417624999424;_LevelValue=800;_MessageID=NCLS-
DEPLOYMENT-00035;|
  [AutoDeploy]自動デプロイは正常に実行されました : H:\java\glassfish4\glassfish\domains\domain1\autodeploy\Hello200.war。|#]

[#|2014-12-04T01:43:19.426+0900|INFO|glassfish 4.0|javax.enterprise.system.tools.deployment.autodeploy|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417624999426;_LevelValue=800;_MessageID=NCLS-
DEPLOYMENT-00027;|
  Selecting file H:\java\glassfish4\glassfish\domains\domain1\autodeploy\Hello251.war for autodeployment|#]

[#|2014-12-04T01:43:27.075+0900|INFO|glassfish 4.0|javax.enterprise.system.tools.deployment.common|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417625007075;_LevelValue=800;|
  visiting unvisited references|#]

[#|2014-12-04T01:43:27.086+0900|INFO|glassfish 4.0|javax.enterprise.system.tools.deployment.common|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417625007086;_LevelValue=800;|
  visiting unvisited references|#]

[#|2014-12-04T01:43:27.091+0900|INFO|glassfish 4.0|javax.enterprise.system.tools.deployment.common|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417625007091;_LevelValue=800;|
  visiting unvisited references|#]

[#|2014-12-04T01:43:27.280+0900|INFO|glassfish 4.0|javax.enterprise.web|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417625007280;_LevelValue=800;_MessageID=AS-WEB-GLUE-00172;|
  Loading application [Hello251] at [/Hello251]|#]

[#|2014-12-04T01:43:27.392+0900|INFO|glassfish 4.0|javax.enterprise.system.core|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417625007392;_LevelValue=800;|
  Hello251は、7,952ミリ秒で正常にデプロイされました。|#]

[#|2014-12-04T01:43:27.392+0900|INFO|glassfish 4.0|javax.enterprise.system.tools.deployment.autodeploy|_ThreadID=119;_ThreadName=AutoDeployer;_TimeMillis=1417625007392;_LevelValue=800;_MessageID=NCLS-
DEPLOYMENT-00035;|
  [AutoDeploy]自動デプロイは正常に実行されました : H:\java\glassfish4\glassfish\domains\domain1\autodeploy\Hello251.war。|#]

glassfishのロード時のvisualvm表示、GCが頻繁に走っているのか、ヒープ使用量のグラフが穏やかでない

結局、約35分にしてロードが終わりました。では、せっかくのアプリケーションサーバーなので管理コンソールを開いてみましょう。。。。

管理コンソールでアプリケーションクリック「長時間実行中のプロセスが検出されました。お待ちください...」と表示されて固まっている

画面が返ってこなくなりました

死んだ魚

勝ち誇るTomcat

でも、TomcatよりもGlassfishはずっと多くの機能を持っているんだから当然といったらそうですね。そもそもこの実験が業務で役立つことって相当レアケースかもしれません。それでも、僕は自分に合ったツールはやはりTomcatなのかなと思い直しました。そう、今なら言える!

_人人人人人人人人人人人人_
> Tomcatかわいい <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄

WildFlyは実験を行った当時はまだリリースしてなかったので試していませんでした。機会があったらやってみようと思います。もしかするとUndertowは優秀らしいので、Tomcatよりも更によい結果をだすかもしれませんね!

本題とは関係ないおまけ:スクリプトにjrnscriptを多用したわけ

今回、WindowsでもLinuxでもMacでも同じコマンドラインを流用できるように、jrunscriptのワンライナーで記述しました。これは、JVM内包のRhinoまたはnashornを使ってシェルのように使えるもので、意外と知られていませんが、下記のドキュメントにあるシェル風のグローバル関数が使えます。
GLOBALS
(これ、見つけづらいところにあるので、毎回探しちゃいます。これからはこの記事を参照すれば探さなくて良くなりますね!)

例えば、今回やったように、cpでコピーしたり、catで簡易curlにしたりできます。これはちょっとしたインストーラのようなものを作るときに便利です。困ったらJavaAPIが使えるので、安心です。なので、僕は普段Windowsを使っていてWSHとかPowershellは苦手なので、ちょっとしたスクリプトを書くときに、jrunscriptをよく使います。

コメントを残す