20201020のJavaに関する記事は9件です。

Javaコマンドについて

そもそもJavaコマンドとは?

背景

業務の中でそもそもJavaコマンドが実行された場合どんな処理がされているのか?

調べてみた。

Javaコマンドの仕組み

  • javaコマンドは、JVM起動するためのコマンド
  • JVMは起動後に、指定されたクラスをロードし、クラスのmainメソッドを呼び出す
  • javaコマンドの構文
java 完全修飾クラス名 [引数 引数 ...]

詳細

  • クラス名の後に続ける引数のことを「起動パラメータ」「コマンドライン引数」という
  • 起動パラメータはスペースで区切って複数指定できる
  • 起動パラメータはオプションなので省略が可能
  • 起動パラメータとして指定されたデータはJVMによってString配列オブジェクトに格納されmainメソッドの引数として渡される

javaコマンドを実行したときの流れ

  • JVMを起動する
  • 指定されたクラスをクラスパスから探し出してロードする
  • String型配列オブジェクトを作成して起動パラメータを格納する
  • 起動パラメータを保持したString配列型オブジェクトへの参照を引数に渡してmainメソッドを実行する

例題

public class Main{
   public static void main(String[] args) {
    System.out.println(args[0] + " " + args[1]);
   }
}

コマンド

$ java Main red blue grenn

Dokojavaで実際にやってみよう!

参考にした記事(いつもありがとうございます。)

【初心者でもすぐわかる】javaコマンドの使い方まとめ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WildFlyで実行可能Jar(UberJarともBootableJarとも言う)

何の記事か

直近の登壇に備えてMicroProfileの現状を調べている際に
WildflyがBootableJar対応したとのリリースノートを見かけました。
Redhatであれば、QuarkusとかThorntailだけでなくてWildflyなの??と
衝撃覚えましたので、記事化してみました

誰向けか

Javaで開発したことある人向けです。
Mavenって何とかの人は、そのあたり知ってから読むと良いです。

きっかけのリリースノート

Wildfly21のリリースノートに以下記載があります。

Bootable JAR / Galleon Layers

The biggest thing in WildFly 21 is Jean-Francois Denise and the team have added GA-level > support for the Bootable JAR feature that we’ve been working on over the last two releases.

・元文書
https://www.wildfly.org/news/2020/10/13/WildFly21-Final-Released/

なんて素晴らしいのでしょう。

実行可能Jar(UberJarともBootableJar)って何?

SpringBootしかやったことがない人は経験ないと思いますが、SpringBoot登場以前は、①アプリケーションサーバなるミドルウェアを起動し、その後に②アプリケーションのアーカイブファイル(warとかearとか)をデプロイすることにより、Webアプリケーションを起動させていました。

image.png

ここでいうアプリケーションサーバが、Wildflyだったわけです。

それが、最近だと事前にアプリケーションサーバを立ち上げる形式ではなく、アプリケーションアーカイバファイル(jar)の中にアプリケーションサーバで必要だったライブラリを包んで、いきなり立ち上げることができるようになったのです。楽ですね。

image.png

ちなみに、Wildfly公式ではBootableJarと読んでますが、世間ではUberJarと呼ぶことが多い気がします。
Uberって何というと、ドイツ語で「従事・同時性」みたいな意味があってそこから来ているそうです。
(これも今回調べて初めて知りました)

やってみる

Wildfly公式ページからチュートリアルをやろうかとも思ったのですが、楽します。
MicroProfileのStarterにWildflyが選べましたのでそこからやります。

https://start.microprofile.io/
に行き、MP3.3(MicroProfileのバージョン3.3を使うという意味)を選ぶとMicroProfile RuntimeにWildFlyが登場するのでそれを選んでダウンロードするだけです。

image.png

Zipファイルを解凍すると、中身は以下のとおりです。

image.png

Readme.mdに従って、

mvn package

を実施し、出来上がったjarファイルに対して

java -jar target/demo-jboss-wildfly.jar

をコマンドライン上から打つと、アプリが立ち上がります。簡単すぎますね。
初回は各種ライブラリのダウンロードが走るので遅いですが、アプリ立ち上げは数秒でした。
SpringBootに引けを取らないぐらい早いですね。

\$ java -jar target/demo-jboss-bootable.jar
20:38:48,068 INFO  [org.wildfly.jar] (main) WFLYJAR0007: Installed server and application in /var/folders/1m/v_pnk3kd3hj0558d1z3nsnzh0000gn/T/wildfly-bootable-server4287071073176360181, took 1736ms
20:38:48,524 INFO  [org.wildfly.jar] (main) WFLYJAR0008: Server options: [--read-only-server-config=standalone.xml]
20:38:48,632 INFO  [org.jboss.msc] (main) JBoss MSC version 1.4.12.Final
20:38:48,641 INFO  [org.jboss.threads] (main) JBoss Threads version 2.4.0.Final
20:38:48,801 INFO  [org.jboss.as] (MSC service thread 1-3) WFLYSRV0049: WildFly Full 21.0.0.Final (WildFly Core 13.0.1.Final) starting
20:38:49,685 INFO  [org.wildfly.extension.microprofile.config.smallrye._private] (ServerService Thread Pool -- 17) WFLYCONF0001: Activating WildFly MicroProfile Config Subsystem
20:38:49,690 INFO  [org.jboss.as.naming] (ServerService Thread Pool -- 19) WFLYNAM0001: Activating Naming Subsystem
20:38:49,695 INFO  [org.wildfly.extension.microprofile.openapi.smallrye] (ServerService Thread Pool -- 18) WFLYMPOAI0001: Activating Eclipse MicroProfile OpenAPI Subsystem
20:38:49,715 INFO  [org.jboss.as.jaxrs] (ServerService Thread Pool -- 16) WFLYRS0016: RESTEasy version 3.13.2.Final
20:38:49,722 INFO  [org.xnio] (ServerService Thread Pool -- 15) XNIO version 3.8.2.Final
20:38:49,741 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-7) WFLYUT0003: Undertow 2.2.2.Final starting
20:38:49,743 INFO  [org.jboss.as.naming] (MSC service thread 1-2) WFLYNAM0003: Starting Naming Service
20:38:49,773 INFO  [org.xnio.nio] (ServerService Thread Pool -- 15) XNIO NIO Implementation Version 3.8.2.Final
20:38:49,818 INFO  [org.wildfly.extension.io] (ServerService Thread Pool -- 15) WFLYIO001: Worker 'default' has auto-configured to 8 IO threads with 64 max task threads based on your 4 available processors
20:38:49,865 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-6) WFLYUT0012: Started server default-server.
20:38:49,868 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-8) WFLYUT0018: Host default-host starting
20:38:49,902 INFO  [org.jboss.as.server.deployment.scanner] (MSC service thread 1-3) WFLYDS0013: Started FileSystemDeploymentService for directory /var/folders/1m/v_pnk3kd3hj0558d1z3nsnzh0000gn/T/wildfly-bootable-server4287071073176360181/standalone/deployments
20:38:49,913 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-8) WFLYSRV0027: Starting deployment of "demo-jboss.war" (runtime-name: "ROOT.war")
20:38:49,996 INFO  [org.wildfly.extension.undertow] (MSC service thread 1-5) WFLYUT0006: Undertow HTTP listener default listening on 127.0.0.1:8080
20:38:50,552 INFO  [org.jboss.weld.deployer] (MSC service thread 1-8) WFLYWELD0003: Processing weld deployment ROOT.war
20:38:50,959 INFO  [org.hibernate.validator.internal.util.Version] (MSC service thread 1-8) HV000001: Hibernate Validator 6.0.21.Final
20:38:51,321 INFO  [org.jboss.weld.Version] (MSC service thread 1-1) WELD-000900: 3.1.5 (Final)
20:38:51,488 INFO  [org.jboss.weld.Bootstrap] (MSC service thread 1-3) WELD-000101: Transactional services not available. Injection of @Inject UserTransaction not available. Transactional observers will be invoked synchronously.
20:38:52,237 INFO  [org.wildfly.extension.microprofile.openapi.smallrye] (MSC service thread 1-8) WFLYMPOAI0004: Registered MicroProfile OpenAPI endpoint '/openapi' for host 'default-host'
20:38:52,600 INFO  [org.jboss.resteasy.resteasy_jaxrs.i18n] (ServerService Thread Pool -- 22) RESTEASY002225: Deploying javax.ws.rs.core.Application: class com.example.demo.jboss.DemojbossRestApplication$Proxy$_$$_WeldClientProxy
20:38:52,639 INFO  [org.wildfly.extension.undertow] (ServerService Thread Pool -- 22) WFLYUT0021: Registered web context: '/' for server 'default-server'
20:38:52,641 INFO  [org.jboss.as.server] (Controller Boot Thread) WFLYSRV0010: Deployed "demo-jboss.war" (runtime-name : "ROOT.war")
20:38:52,669 INFO  [org.jboss.as.server] (Controller Boot Thread) WFLYSRV0212: Resuming server
20:38:52,671 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: WildFly Full 21.0.0.Final (WildFly Core 13.0.1.Final) started in 4140ms - Started 139 of 144 services (22 services are lazy, passive or on-demand)

→ 4140msec !!!

image.png

MicroProfileのサンプルアプリトップページが無事表示できました。

実はMicroProfileのStarterで落としたものは、最初に紹介したWildfly21ではなかったです。
pom.xmlを見ると

...
<version.wildfly>19.1.0.Final</version.wildfly> 
...

と19.1でしたので、そのバージョンから対応していたことになりますね。
せっかく新しいバージョンが出たので21に今回は変更して動かしました。

...
<version.wildfly>21.0.0.Final</version.wildfly>
...

あと、Jarを作るプラグインについても併せて変えないといけないと思いましたがどう設定すればよいのか分からず、ひとまずバージョン指定なしにして動かしています。

...
      <build>
        <plugins>
          <plugin>
            <groupId>org.wildfly.plugins</groupId>
            <artifactId>wildfly-jar-maven-plugin</artifactId>
            <!-- 最新バージョンが何か分からず、一旦外して最新で実行-->
            <!--version>1.0.0.Alpha4</version-->
            <executions>
...

まとめ

Wildflyで実行可能Jarが実現できるという認知度が低いかと思ったので記事化してみました。
プロダクト環境を変えるのは運用上、すぐには難しいとかあるかもしれませんが、ローカル開発環境から変えるとか
プロトタイプ開発から使ってみるとか、いろいろステップ踏みながらやっていきたいですね。

参考

使ったソースコードはGithubにあげています。ご参考です。
https://github.com/omix222/wildfly21demo

過去MicroProfileの概要についてまとめた記事です。
https://qiita.com/omix222/items/50804e30e43085ceb912

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

☾ Java / 例外処理


 いつまでも苦手シリーズ第2弾。

✴︎ 例外とは

 プログラムはコンパイルが成功しても、実行した際にエラーが発生することがあります。Javaでは、実行時に発生したエラーを例外と呼びます。また例外が発生することを例外がスローされるといいます。

✴︎ 例外処理

 例外がスローされた際に、プログラム側でその例外に対する処理を何も記述していないとプログラムが強制終了してしまいます。

 以下のSample.javaでは、用意された配列の要素外にアクセスするため、例外が発生します。

1¥Sample.java
public class Sample {
  public static void main(String[] args) {
    int[] num = {10, 20, 30};
    for (int i=0; i<4; i++) {
      System.out.print("num :" + num[i]);
      System.out.println(" : " + (i+1) + "回目のループ"); 
    }
    System.out.println("--end--");
  }
}
実行結果
num : 10 : 1回目のループ
num : 20 : 2回目のループ
num : 30 : 3回目のループ
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
        at Sample.main(Sample.java:5)

 このSample.javaでは配列を用意しfor文を使用して各要素を出力しています。しかし、4回目のループで配列の要素外にアクセスしているためJava実行環境から例外がスローされてしまいます。実行結果を見ると、8行目の「--end--」の出力がされていないことから、プログラムが途中で終了していることがわかります。
 稼働し続けるアプリケーションでは、実行時に起こりうるエラーによって異常終了することを未然に防がなくてはいけません。そのためJavaでは、例外処理というメカニズムを使用します。例外処理は、例外が起きた場合の対処を記述し、これによりプログラムは強制終了されることなく、実行を継続することができます。

✴︎ checked例外とunchecked例外

 Javaの例外クラスはchecked例外unchecked例外の2種類に分類されます。checked例外は、DBなどJava実行環境以外の環境が原因で発生する例外です。一方unchecked例外は、実行中のプログラムが原因で発生する例外(実行時例外)や、メモリ不足などプログラムの例外処理では復旧できない例外です。
 checked例外の特徴は、例外処理が必須であることです。一方、unchecked例外は、例外処理が必須ではなく任意であることです。
 さらに、例外クラスは次の3種類に分類されます。

  • Errorクラスおよびそのサブクラス(unchecked例外)
  • RuntimeExceptionクラスおよびそのサブクラス(unchecked例外)
  • RuntimeExceptionクラス以外のExceptionのサブクラス(checked例外)

スクリーンショット 2020-10-20 9.23.23.JPG

 Throwableクラスは例外クラスのルートクラスです。ThrowableクラスのサブクラスとしてErrorクラスとExceptionクラスが提供されています。
 Errorクラスは、メモリ不足などJava実行環境で発生する致命的なエラーを表現し、この例外をアプリケーションで対応するのは困難であるため、このクラスおよびサブクラスの例外に対する処理は任意となっています。
 Exceptionクラスは、アプリケーションで発生するエラーを表現し、「プログラムからファイルに書き込みをしようとしたがファイルが存在しなかった」「配列の要素外にアクセスをした」などがあげられます。
 ただし、コンパイルには成功するため、これらの例外に対する処理を必ず記述しておかなければならない場合と、記述しなくてもよい場合の2通りがあります。この判断は、処理内容ではなく処理した結果、発生する可能性のある例外クラスが何であるかによって決定します。RuntimeExceptionクラスおよびそのサブクラスでる場合には任意であり、RuntimeExceptionクラス以外のExceptionのサブクラスである場合には必須です。

✴︎ 主な例外クラス

 以下はJavaで提供されている例外クラスの一部です、例外クラス名およびunchecked例外、checked例外のどちらに含まれているかを中心に把握しておきます。また、太字で記載した例外クラスは出題頻出度が高いため、どのような時に発生する例外かを確認しておくとよいでしょう。

1. Errorのサブクラス(unchecked例外:例外処理は任意)

クラス名 説明
AssertionError assert文を使用している際に、boolean式でfalseが返ると発生
OutOfMemoryError ガベージ・コレクタが稼働しても使用可能なメモリをこれ以上確保できないメモリ不足時に発生
StackOverflowError アプリケーションでの再帰の回数が多すぎる場合に発生
NoClassDefFoundError 読み込もうとしたクラスファイルが見つからない場合に発生

2. RuntimeExceptionのサブクラス(unchecked例外:例外処理は任意)

クラス名 説明
ArrayIndexOutOfBoundsException 不正なインデックスで要素アクセスしようとした場合に発生
ClassCastException 参照変数において間違ったキャストを行った場合に発生
IllegalArgumentException メソッドの引数に不正なものを使用した場合に発生
ArithmeticException 整数をゼロで除算した場合に発生
NullPointerException nullが代入されている参照変数に対して、メソッド呼び出しを行った場合に発生
NumberFormatException 整数を表さない文字列を整数に変換しようとした場合に発生

3. RuntimeException以外のExceptionのサブクラス(checked例外:例外処理は必須)

クラス名 説明
IOException 入出力において何らかの失敗があった場合に発生
FileNotFoundException ファイル入出力において、目的のファイルがなかった場合に発生
ClassNotFoundException クラス名を表す文字列を引数にメソッドを使用して、クラスを読み込もうとしたが、そのクラスファイルが見つからない場合に発生

✴︎ 独自例外クラスの定義

 Javaは多くの例外クラスを提供していますが、プログラマが独自で例外クラスを定義することも可能です。一般的にはExceptionクラスを継承したpublicなクラスとして定義します。

Sample.java
[修飾子] class クラス名 extends Exception { }
 public class MyException extends Exception { }

 Exceptionクラスを継承することで、ExceptionクラスおよびそのスーパークラスであるThrowableクラスが提供しているメソッドを引き継ぐことになります。Throwableクラスには、例外からエラーメッセージを取り出すメソッドや、エラーを追跡し発生箇所を特定する(エラートレース)メソッドなどが提供されています。

Throwableの主なメソッド

クラス名 説明
void printStackTrace() エラーを追跡し発生箇所を特定するエラートレースを出力する
String getMessage() エラーメッセージを取得する

✴︎ try-catch-finallyブロック

 Javaでの例外処理を行う方法には2通りあります。

  • try-catch-finallyブロックによる例外処理
  • throwsキーワードによる例外処理

 この節では、try-catch-finallyブロックを使用した例外処理を見ていきます。try-catch-finallyブロックは、tryブロック、catchブロック、finallyブロックから構成されており、各ブロックには以下のように処理を記述します。

Sample.java
void method() {
  try {
  例外が発生しそうな処理;
  } catch (例外クラス名 変数名) {
  例外が発生したときの処理;
  } finally {
  必ず実行したい処理;
  }
}

 各ブロックの役割は以下のとおりです。

  • tryブロック : 例外が発生しそうな箇所をtryブロックで囲む
  • catchブロック : 例外が発生したときの処理をcatchブロックの中に定義する
  • finallyブロック : 例外が発生してもしなくても必ず実行したい処理をfinallyブロックに定義する

 Java実行環境からスローされてきた例外オブジェクトを受け取るのが、try-catch-finallyブロックの役割です。catchブロックは次の構文で記述します。

Sample.java
catch (例外クラス名 変数名) { }
 catch (ArrayIndexOutOfBoundsException e) { …… }

 1¥Sample.javaではArrayIndexOutOfBoundsExceptionという例外がスローされているので、以上の例のように定義します。
 なお、try-catch-finallyのすべてのブロックを記述する必要はありません。以下の組合せが可能です。

  • try-finally
  • try-catch-finally

 では、try-catchブロックを使用して、前のサンプルに例外処理を追加してみましょう。

2¥Sample.java
public class Main {
  public static void main(String[] args) {
    int[] num = {10, 20, 30};
    for (int i=0; i<4; i++) {
      try {
        System.out.print("num :" + num[i]);
        System.out.println(" : " + (i+1) + "回目のループ");
      } catch (ArrayIndexOutOfBoundsException e) {    // 8行目
        System.out.println("例外が発生しました");
      }
    }
    System.out.println("--end--");    // 12行目
  }
}
実行結果
num : 10 : 1回目のループ
num : 20 : 2回目のループ
num : 30 : 3回目のループ
例外が発生しました
--end--

 1¥Sample.javaと同様に4回目のループで例外が発生していますが、8行目のcatchブロックで例外オブジェクトがキャッチされています。その結果、プログラムは強制終了することなく、続けて12行目の処理も実行されていることが確認できます。

 finallyブロックは、例外が発生してもしなくても、必ず行いたい処理があれば利用します。たとえば、リソースの解放(データベースのclose処理やファイルのclose処理など)があげられます。

✴︎ 複数のcatchブロック定義

 tryブロック内で発生する可能性のある例外クラスが複数ある場合に対応するため、catchブロックは複数定義できます。

Sample.java
} catch (例外クラス名 変数名) {
  処理;
} catch (例外クラス名 変数名) { }

 ただし、catchブロックで指定した例外クラス間に継承関係がある場合は、サブクラス側から記述します。スーパークラスから記述するとコンパイルエラーとなります。

✴︎ throws

 try-catch-finallyブロックによる例外処理の他に、throwsキーワードによる例外処理が可能です。この方法では、例外が発生する可能性のあるメソッドを定義するとき「throws 発生する例外クラス名」を指定しておきます。これにより、throws指定された例外クラスのオブジェクトがメソッド内で発生した場合、その例外オブジェクトは、メソッドの呼び出し元に転送されます。
 構文は以下のとおりです。

Sample.java
[修飾子] 戻り値の型 メソッド名 (引数リスト) throws 例外クラス名 { }

 例外クラス名には、このメソッド内で発生する可能性のある例外で、メソッド呼び出し元に転送したい例外クラス名を指定します。指定する例外クラスが複数ある場合は、例外クラス名を「,」(カンマ)で区切って書き並べます。

3¥Sample.java
class Test {
  void loop() throws ArrayIndexOutOfBoundsException {
    int[] num = {10, 20, 30};
    for (int i=0; i<4; i++) {
      System.out.println("num : " + num[i]);
    }
  }
}

public class Sample {
  public static void main(String[] args) {
    try {
      Test t = new Test();
      t.loop();
    } catch (ArrayIndexOutOfBoundsException e) {
      System.out.println("例外が発生しました");
    }
    System.out.println("--end--");
  }
}
実行結果
num : 10
num : 20
num : 30
例外が発生しました
--end--

 Testクラスのloop()メソッドは明示的にArrayIndexOutOfBoundsException例外をスローするように定義されています。実行結果を見ると、loop()メソッド側で発生した例外がmain()メソッド側に転送され、main()メソッドのcatchブロックがそれを受け取っていることがわかります。
 なお、「throws ArrayIndexOutOfBoundsException」の部分を削除したとしても、ArrayIndexOutOfBoundsException例外をスローし、実行結果は同じになります。これは発生した例外クラス(ArrayIndexOutOfBoundsException)がunchecked例外であり、例外処理が必須ではないため、問題なくコンパイル、実行ができるのです。

✴︎ throw

 例外は、Java実行環境がスローするだけでなく、throwキーワードを使用してプログラム内で明示的にスローすることもできます。前述のthrowsキーワードとよく似ていますが、混乱しないよう注意しましょう。
 throwキーワードを使用すると、Javaで提供されている例外クラスや独自例外クラスをインスタンス化した例外オブジェクトを、任意の場所でスローできます。

Sample.java
throw 例外オブジェクト;
例1 throw new IOException();
例2 IOException e = new IOException();
    throw e;

 以下では独自例外クラスとしてMyExceptionクラスを定義しています。そしてSampleクラスのcheckAge()メソッド内では、引数で0よりも小さい値が渡された場合、MyExceptionオブジェクトを生成し、throwキーワードで明示的にスローしています。

4¥Sample.java
class MyException extends Exception {    // 独自例外クラス
  private int age;
  public void setAge(int age) { this.age = age; }
  public int getAge() { return this.age; }
}

public class Sample {
  public static void main(String[] args) {
    try {
      int age = -10;
      checkAge(age);    // 11行目
    } catch (MyException e) {    // 12行目
      System.out.println("不正な値です。age : " + e.getAge());    // 13行目
    }
  }
  public static void checkAge(int age) throws MyException {    // 16行目
    if (age>=0) {
      System.out.println("OK");
    } else {    // 19行目
      MyException e = new MyException();    // 20行目
      e.setAge(age);
      throw e;    // 22行目
    }
  }
}
実行結果
不正な値です。age : -10

 11行目で、16行目に定義したcheckAge()メソッドを呼び出しています。17行目では引数で受け取ったage変数の値が0以上かどうかを評価しています。もし、0以上でなかった場合は19行目以下に制御が移ります。そして、1行目で用意しておいた独自例外クラスを20行目でインスタンス化し、22行目で明示的にスローしています。スローされた例外は、checkAge()メソッドの呼び出し元の12行目でキャッチされ、13行目でメッセージが出力されます。

 オーバーライドの注意点はまた今度。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JMeterでグラフィカルに変幻自在なアクセス負荷を設定する(後編)

はじめに

前回はJMeterにThroughputShapingTimer(プラグイン)を組み込んで、
グラフィカルに負荷量を調整する方法を説明しました。
JMeterでグラフィカルに変幻自在なアクセス負荷を設定する(前編)
sample_image
↑アクセス負荷量の制御イメージ 公式ページより

簡単な使用法の紹介までで話が長くなってしまったので、今回は実践的な使い方を説明します。

前回のあらすじ

上司さんからWebサイトの性能試験を任されたA君。
「Twitterでバズった時のアクセス急上昇を試験したい」という上司からの無茶振りに対し、
ThroughputShapingTimerを使うことでA君はJMeterで期待に添う試験ができるようになりました。

しかしA君はまだ単一ページへの試験しか検証できていません。
Webサイトのアクセス実績に基づき、より複雑な試験シナリオの作成に奮闘するのであった。。。

Throughput Shaping Timer を使ってみる(応用編)

小ネタはこのくらいにしておき、早速試験パターンとJMeterの試験シナリオ作成を説明していきます。

この記事では以下3つの試験パターンをホップ・ステップ・ジャンプの流れでご紹介します。

  • 1つの画面遷移パターンをシリアルに実行する
  • 1つの画面遷移パターンをパラレルに実行する
  • 複数の画面遷移パターンをパラレルに実行する

環境情報

  • Mac(Catalina)
  • Apache httpd server(2.4.x)
  • Java8(Oracle java 8u202)
  • JMeter(5.3)

環境復旧

Apacheが停止している場合は前回と起動手順が異なるので、備忘かねて残します。

疎通確認

JMeterからリクエストを送信することでも確認できますが、ここではCURLコマンドで確認します。

$ curl http://localhost:8080/index.html
curl: (7) Failed to connect to localhost port 8080: Connection refused

Failedとなった場合、前回使用したApacheに通信できていないことがわかります。

コンテナの状態確認

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS                  PORTS               NAMES
e2dc7c3714ee        httpd:2.4           "httpd-foreground"   6 days ago          Exited (0) 5 days ago                       my-apache-app

StatusExitedになっているので、コンテナが停止状態(正常)であることがわかります。

万が一Upになっている場合はハング状態なので、一度コンテナを停止させます。

コンテナの停止(参考)

$ docker stop e2dc7c3714ee

コンテナIDを指定して停止します。
コマンド実行後に改めてコンテナ状態を確認し、Exitedになっていることを確認します。
(ダメだったらPC再起動ですね)

コンテナの起動

$ docker start e2dc7c3714ee
e2dc7c3714ee
$ docker ps -a             
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                                           NAMES
e2dc7c3714ee        httpd:2.4           "httpd-foreground"   6 days ago          Up 3 seconds        0.0.0.0:8080->80/tcp, 0.0.0.0:32772->8080/tcp   my-apache-app
$ curl http://localhost:8080/index.html
hello

これで準備完了です。

1つの画面遷移パターンをシリアルに実行する

Amazonさんを例に挙げると、ほとんどの人が以下の流れでサイトを利用しますね

  1. トップページを表示する
  2. 商品を選択する
  3. 購入する

本パターンでは、ひとりのユーザが同じページ遷移を延々と繰り返す前提で負荷量調整をする方法を説明します。

状況設定

  • ユーザはひとり
  • ページ01 -> ページ02 -> ページ03 の順に画面遷移する
  • 普段は15RPS(RequestPerSecond)だが、瞬間的に75RPSまで上昇する

アクセスの流れをJMeterで表現する

前回作成したスレッドグループを流用します
今回は試験用にコンテンツを用意していないので、同一のHTMLにクエリ文字を変えてアクセスすることで、3種類のURLへアクセスしていることにします。

ページ01
スクリーンショット 2020-10-18 22.08.16.png

ページ02
スクリーンショット 2020-10-18 22.14.32.png

ページ03
スクリーンショット 2020-10-18 22.15.41.png

ThroughputShapingTimerを設定する

スクリーンショット 2020-10-18 22.17.48.png

負荷量を変更しています。

スレッドグループを設定する

スクリーンショット 2020-10-18 22.43.35.png

スレッド数(並列度)を1に設定します。

JMeterを実行する

スクリーンショット 2020-10-18 22.22.39.png

3つのURLに大体均等にアクセスできましたね。
(完全に均等でないですが、ThroughputShapingTimerちゃんはお茶目なので許してあげてください)

Apacheログ解析

$ docker logs e2dc7c3714ee | tail -1094 | awk '{print $4}' | sed 's_\[18/Oct/2020:__' | uniq -c
〜〜〜
  16 13:20:00
  14 13:20:01
  16 13:20:02
  14 13:20:03
  16 13:20:04
  14 13:20:05
  15 13:20:06
  26 13:20:07
  38 13:20:08
  50 13:20:09
  63 13:20:10
  75 13:20:11
  63 13:20:12
  51 13:20:13
  39 13:20:14
  27 13:20:15
  16 13:20:16
  14 13:20:17
  15 13:20:18
  14 13:20:19
〜〜〜

15RPSから75RPSまで上昇し、15RPSまで戻りましたね。

※これから何度か試験するので、今回のアクセス総数である1094行のログを確認対象にしています
※sedコマンドの区切り文字はスラッシュ以外も指定できるよ!とのアドバイスをいただき見やすくなりました

Apacheログ解析(シリアルであることを確認)

$ docker logs e2dc7c3714ee | tail -10 | awk '{print $4, $7}'
[18/Oct/2020:13:20:36 /index.html?test02
[18/Oct/2020:13:20:36 /index.html?test03
[18/Oct/2020:13:20:36 /index.html?test01
[18/Oct/2020:13:20:36 /index.html?test02
[18/Oct/2020:13:20:36 /index.html?test03
[18/Oct/2020:13:20:36 /index.html?test01
[18/Oct/2020:13:20:36 /index.html?test02
[18/Oct/2020:13:20:36 /index.html?test03
[18/Oct/2020:13:20:36 /index.html?test01
[18/Oct/2020:13:20:36 /index.html?test02

綺麗にページ01 -> ページ02 -> ページ03 の順でアクセスが来ていることがわかりますね。

1つの画面遷移パターンをパラレルに実行する

前回はひとりのユーザが同じページ遷移を延々と繰り返す前提で負荷量調整しましたが、実際のWebサイトではあり得ませんよね。

3人のユーザが同時に前回同様の画面遷移をする前提で負荷量調整をする方法を説明します。

状況設定

  • ユーザは3人
  • ページ01 -> ページ02 -> ページ03 の順に画面遷移する
  • 普段は15RPS(RequestPerSecond)だが、瞬間的に75RPSまで上昇する

スレッドグループを設定する

スクリーンショット 2020-10-18 23.41.35.png

スレッド数(並列度)を3に設定します。

※他の設定は前回同様です

JMeterを実行する

スクリーンショット 2020-10-18 23.45.47.png

今回も3つのURLに大体均等にアクセスできましたね。

Apacheログ解析

$ docker logs e2dc7c3714ee | tail -1082 | awk '{print $4}' | sed 's_\[18/Oct/2020:__' | uniq -c
〜〜〜
  14 14:44:50
  15 14:44:51
  14 14:44:52
  15 14:44:53
  15 14:44:54
  14 14:44:55
  16 14:44:56
  14 14:44:57
  16 14:44:58
  13 14:44:59
  28 14:45:00
  38 14:45:01
  50 14:45:02
  65 14:45:03
  75 14:45:04
  63 14:45:05
  51 14:45:06
  37 14:45:07
  28 14:45:08
  15 14:45:09
  15 14:45:10
  15 14:45:11
  15 14:45:12
  15 14:45:13
〜〜〜

15RPSから75RPSまで上昇し、15RPSまで戻りましたね。

Apacheログ解析(パラレルであることを確認)

$ docker logs e2dc7c3714ee | tail -10 | awk '{print $4, $7}'
[18/Oct/2020:14:45:29 /index.html?test03
[18/Oct/2020:14:45:29 /index.html?test02
[18/Oct/2020:14:45:29 /index.html?test01
[18/Oct/2020:14:45:29 /index.html?test02
[18/Oct/2020:14:45:29 /index.html?test03
[18/Oct/2020:14:45:29 /index.html?test02
[18/Oct/2020:14:45:29 /index.html?test03
[18/Oct/2020:14:45:29 /index.html?test01
[18/Oct/2020:14:45:29 /index.html?test03
[18/Oct/2020:14:45:29 /index.html?test01

前回に比べてアクセス順が ページ01 -> ページ02 -> ページ03 の順ではなくなりましたね。
3人のユーザが同時にアクセスして順序が前後したことがわかります。

複数の画面遷移パターンをパラレルに実行する

前回までは以下のパターンを試験してみました。(パターン1)
1. トップページを表示する
2. 商品を選択する
3. 購入する

今回は以下の画面遷移を別のスレッドグループとして追加します。(パターン2)
1. トップページを表示する
2. 商品を選択する
3. 他のおすすめ商品を選択する

今回は
(パターン1)3人のユーザが同時に画面遷移をする
(パターン2)5人のユーザが同時に画面遷移をする
前提で負荷量調整をする方法を説明します。

状況設定

(パターン1)※前回同様

  • ユーザは3人
  • ページ01 -> ページ02 -> ページ03 の順に画面遷移する
  • 普段は15RPS(RequestPerSecond)だが、瞬間的に75RPSまで上昇する

(パターン2)

  • ユーザは5人
  • ページ11 -> ページ12 -> ページ13 の順に画面遷移する
  • 50RPS(RequestPerSecond)が継続的に発生する

アクセスの流れをJMeterで表現する

前回作成したスレッドグループを流用します。

以下の写真のように、スレッドグループを複製して配置します。
統計レポートはスレッドグループと同じ階層に配置してください。

スクリーンショット 2020-10-19 2.53.12.png

複製した設定を以下のように設定変更します。

スレッドグループ:
 スレッド数:
HTTP リクエスト(test11):
 パス:/index.html?test11
HTTP リクエスト(test12):
 パス:/index.html?test12
HTTP リクエスト(test13):
 パス:/index.html?test13

ThroughputShapingTimerを設定する

スクリーンショット 2020-10-19 23.54.09.png

50RPSで均一にします。
※JMeterはいきなりフルパワーを発揮できないので、指定負荷量に到達するまで徐々に駆け上がるようにしましょう

JMeterを実行する

スクリーンショット 2020-10-20 0.13.33.png

スレッドグループ1と2それぞれで均一の負荷量が保てましたね。

JMeterレポートを作成する

今回は試験内容が複雑なので、Apacheログの解析では期待通り動いているかの確認が困難です。
JMeterの標準機能でレポート出力し、結果を確認しましょう。

  1. JMeterレポートのグラフ描写間隔を調整する
    スクリーンショット 2020-10-20 1.47.27.png
    いきなり通常は実施しない手順ですが。。。
    レポートはデフォルトでは1分単位でグラフを描写します。
    今回は試験時間がとても短いので、グラフの描写間隔も短くします。

    <JMeterを展開したディレクトリ>/apache-jmeter-5.3/bin/user.propertiesを開きます。
    76行目あたりにjmeter.reportgenerator.overall_granularityというグラフの描写間隔の設定値があるので、コメントアウトを外して、60000ミリ秒から1000ミリ秒に変更します。

  2. レポート生成メニューを開く
    スクリーンショット 2020-10-20 1.28.33.png
    メニューバーからTools->Generate HTML reportを選択します。

  3. レポート生成に必要な情報を入力する
    スクリーンショット 2020-10-20 1.31.41.png
    Result file (csv or jtl):統計レポートで指定した出力ファイル名
    user.properties file:bin配下にあります(先ほど書き換えたファイル)
    Output directory:どこでもOKです

  4. レポートを生成する
    スクリーンショット 2020-10-20 1.32.35.png
    下部のGenerate reportをクリックしてチェックマークがつけば生成完了です。

JMeterレポートを確認する

  1. 生成されたレポートをブラウザで開く
    スクリーンショット 2020-10-20 1.52.04.png
    先ほどOutput directoryに指定した場所にレポートが生成されているので、index.htmlを開きます。

  2. 目的のグラフに遷移する
    スクリーンショット 2020-10-20 1.53.28.png
    Throughputを選択します。

  3. 目的のグラフに遷移する
    スクリーンショット 2020-10-20 1.55.11.png
    Transactions Per Secondsを選択します。

  4. 美しいグラフに感動する
    スクリーンショット 2020-10-20 1.56.37.png
    ThroughputShapingTimerで設定した通りの波形が見事に描けましたね!!

    おめでとうございます、これであなたもJMeterマスターです!

後日談

A君「ついにJMterの設定が完了しました!」
上司「ありがとう、今回の性能試験はA君に一任するよ。頑張って!」
A君「はい!」

見事試験対象のWebアプリケーションを性能測定する準備ができたA君。
しかしここからが本当の地獄の始まりでした。
まだまだ経験が浅いA君は、これから立ちふさがるであろう数々の問題をまだ予想できていないのでした。。。

続編は予定しておりませんが、、、あえて次回タイトルをつけるのであれば
次回:「性能未達も、本番障害も、あるんだよ」

性能試験は問題発見の手段でしかありません。
A君の試験計画を上司さんがより妥当なものへと導き、A君が大小問わず様々な問題に対して関係者を巻き込んでいくことが、ハッピーエンドへの近道ですね。

A君はさておき、この記事でより豊かなJMeterライフを送れる方が一人でも増えたら嬉しいです。

Appendix

記事を書いていく中で「こういう点でつまずくかもしれない。。。」と思ったポイントをQA形式でまとめます。
他にも気になることがありましたらコメントください。

Q1 設定した負荷量に到達しない

A君「設定は正しいのですが、想定の負荷量に達しません。。。設定値を若干下回ります。。。」
上司「仕様?だよ」

今回の検証では少量の負荷しかかけていないので問題になりませんでしたが、数百RPSを超えたあたりから実測値が設定を若干下回ります。

OSSなのでそこは大目に見て、下回ることを前提に若干大目の負荷量を設定しましょう。

Q2 設定した負荷に全然到達しません。。。

A君「本番相当の負荷量を設定したのですが、実測値が大幅に下回ります。。。JMeterもエラーを吐いています。。。」
上司「エラーを見ないとなんとも言えないが、JMeterが限界に達しているかもしれないね」

JMeterはJavaで動くので、かなりリソースを消費します。
まずはJMeterに割り当てるメモリを増やしてみてください。

参考:
JMeterでOutOfMemoryが発生した場合の対応方法

メモリに余裕はあるが想定の負荷量に到達しない場合は、OSの接続上限数に達している可能性があります。
ファイルディスクリプタの上限数を引き上げてみましょう。
(よくわからない場合は基盤担当の人にやってもらいましょう!)

参考:
reboot/shutdown後も設定を維持したい場合はulimit -nではなく/etc/security/limits.confを編集する(「Too many open files」「ファイルを開きすぎています」エラー対策)

Q3 どう頑張っても期待の負荷量が出ません。。。

A君「いろいろ設定を見直したのですが、本番相当の負荷量が出ません。。。」
上司「JMeterサーバの限界かもしれないね。。。」

大規模システムは複数のサーバで処理を分散するように、1台のJMeterで出せる負荷量には限界があります。(一概には言えませんが。。。)
以下いずれかの方法でJMeterの処理を分散させましょう。

例:10台のJMeterに処理を分散する場合

  • ThroughputShapingTimerの設定値を1/10にして10台のJMeterに配置して実行する
  • 上記に加えてJMeterをMaster-Slave構成にする

参考:
AWSでJMeterのMaster/Slave環境を構築(CentOS 7)
FireWallとKeyStoreがミソですね

Master-Slave構成にすることで実行ログがMasterに集約されるので、レポート作成が楽になるのがメリットですね。
しかしサーバインスタンスが1台増えるので、コスト面がデメリットです。

さいごに

長くなってしまいましたが、今回の記事はこれで完結です。

記事を書くって結構大変だな、ということを実感しました。

今までQiita等々には何度も助けられていますが、なんとなく流し読みするばかりでした。
これからはもう少し噛み締めながら記事を拝見させていただこうと思います。

今回は性能試験に関する記事を掲載しましたが、本業はJavaフレームワークのカスタマイズをしています。
(SpringFrameworkとか)
Javaフレームワークは業務に関わる内容が多く記事にしにくいので、
これを機に今まで手をつけてこなかった分野に挑戦するのもありかなと思っています。

GitHubPagesとGitHubActionsでWebページでも作ってみようかなと思うのですが、
基盤->Javaフレームワークときて画面は一切触ったことないので、失踪しないよう頑張ります。

参考

ThrouputShapingTimer
JMeter generating-dashboard

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ApacheとTomcat

まとめ備忘録

ApacheとTomcatについて


そもそもTomcatとは?


そもそもApacheとは?


Apache, Tomcatでのシステム構成


Tomcatのみの構成

  • Tomcatの役割は「Webサーバ」と「サーブレットコンテナ」。

Apacheと連携した構成

  • Apacheの役割は「Webサーバ」
  • Tomcatの役割は「サーブレットコンテナ」

まとめ


参考にした記事(いつもありがとうございます。)

【社内勉強会】ApacheとTomcat(2017/03/09)

「Tomcat」と「Apache」の違い

Tomcatとは?使い方を分かりやすく解説!初心者向けのインストール手順も確認。Apacheと連携するメリットも紹介

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Cobol からJAVAへのマイグレーション

ファイルを比較する際に、注意事項
ファイルの読み取りが終わると、
MOVE HIGH-VALUE TO fileName-REC
EOF これから、このファイルは最大値がある

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jar ファイルを実行時に「QuantumRenderer: no suitable pipeline found」エラーが出てしまったら

Eclipce で JavaFX というフレームワークを使ってアプリを作成したとき、完成したアプリを jar ファイルにして実行すると開けなくて、代わりに以下のようなエラーが出現しました。

コマンドライン画面
>java -jar test.bat
Graphics Device initialization failed for :  d3d, sw
Error initializing QuantumRenderer: no suitable pipeline found
java.lang.RuntimeException: java.lang.RuntimeException: Error initializing QuantumRenderer: no suitable pipeline found
        at com.sun.javafx.tk.quantum.QuantumRenderer.getInstance(QuantumRenderer.java:280)
        at com.sun.javafx.tk.quantum.QuantumToolkit.init(QuantumToolkit.java:244)
        at com.sun.javafx.tk.Toolkit.getToolkit(Toolkit.java:261)
        at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:267)
        at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:158)
        at com.sun.javafx.application.LauncherImpl.startToolkit(LauncherImpl.java:658)
        at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:678)
        at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
        at java.base/java.lang.Thread.run(Thread.java:832)
Caused by: java.lang.RuntimeException: Error initializing QuantumRenderer: no suitable pipeline found
        at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.init(QuantumRenderer.java:94)
        at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:124)
        ... 1 more
Exception in thread "main" java.lang.reflect.InvocationTargetException
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:564)
        at org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader.main(JarRsrcLoader.java:61)
Caused by: java.lang.RuntimeException: No toolkit found
        at com.sun.javafx.tk.Toolkit.getToolkit(Toolkit.java:273)
        at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:267)
        at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:158)
        at com.sun.javafx.application.LauncherImpl.startToolkit(LauncherImpl.java:658)
        at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:678)
        at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
        at java.base/java.lang.Thread.run(Thread.java:832)

 このエラーの原因はズバリ、 環境変数を通してない です。

Java を利用するときは、「PATH」と「JAVA_HOME」という環境変数を OpenSDK のフォルダパスに設定します。おなじように、フレームワークを利用するときも 環境変数「PATH」にフレームワークのフォルダパスを追加してあげなければいけなかったんです。

 JavaFX の設定は簡単で、Windows の「システム環境変数の設定」を開いて、そこにある「PATH」変数に JavaFX の SDK フォルダの bin フォルダのパスを追加してあげるだけでした。

 この「no suitable pipeline found」は、環境変数の設定が間違っていると、他のフレームワークでも同じ現象になります。このエラーが出た際は、ぜひ環境変数を疑ってみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SLF4Jとは

【背景】

エラーでつまづきそもそもSLF4Jとは何だ?
という所から調べてみた。

【エラー文】

java.lang.NoClassDefFoundError: org/slf4j/LoggerFactory

【直訳】
Simple Logging Facade for Java (SLF4J) は、様々なロギングフレームワーク (java.util.logging, logback, log4j など) のためのシンプルなファサードまたは抽象化として機能し、
エンドユーザがデプロイ時に希望するロギングフレームワークをプラグインすることを可能にする。

ライブラリをSLF4Jで有効にすることは、単一の必須の依存関係、
すなわちslf4j-api.jarを追加することを意味することに注意してください。
もしクラスパスにバインディングが見つからない場合、SLF4J はデフォルトで動作しない実装になります。

JavaソースファイルをSLF4Jに移行したい場合は、
数分でSLF4J APIを使用するようにプロジェクトを移行できるマイグレータツールを検討してください。

あなたが依存している外部的に保守されているコンポーネントがSLF4J以外のロギングAPIを使用している場合、commons logging、log4j、java.util.loggingのように、レガシーAPIのためのSLF4Jのバイナリサポートを見てみてください。

※注意点
クラスパスにバインディングが見つからない場合、SLF4J はデフォルトで動作しない実装になる。

まとめ

参考にした記事(いつもありがとうございます。)

SLF4J、Logback、Log4Jの関係を挙動とともに整理する

Simple Logging Facade for Java (SLF4J)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SLF4Jとは?

【背景】

エラーでつまづきそもそもSLF4Jとは何だ?
という所から調べてみた。

【エラー文】

java.lang.NoClassDefFoundError: org/slf4j/LoggerFactory

SLF4Jとは?

SLF4Jは、ロギングシステム用のシンプルなファサードであり、エンドユーザーが展開時に目的のロギングシステムをプラグインできるようにします。

ファサードとは?

Facadeクラスとは、使い方が複雑になっているクラス群をまとめ、使いやすい形のインターフェースとして外部に提供するクラスのことを指します。
デザインパターンでは、このFacadeクラスを利用するパターンをFacadeパターンと呼びます。
(ちなみに、Facadeは「窓口」を意味する単語です)

【直訳】
Simple Logging Facade for Java (SLF4J) は、様々なロギングフレームワーク (java.util.logging, logback, log4j など) のためのシンプルなファサードまたは抽象化として機能し、
エンドユーザがデプロイ時に希望するロギングフレームワークをプラグインすることを可能にする。

ライブラリをSLF4Jで有効にすることは、単一の必須の依存関係、
すなわちslf4j-api.jarを追加することを意味することに注意してください。
もしクラスパスにバインディングが見つからない場合、SLF4J はデフォルトで動作しない実装になります。

JavaソースファイルをSLF4Jに移行したい場合は、
数分でSLF4J APIを使用するようにプロジェクトを移行できるマイグレータツールを検討してください。

あなたが依存している外部的に保守されているコンポーネントがSLF4J以外のロギングAPIを使用している場合、commons logging、log4j、java.util.loggingのように、レガシーAPIのためのSLF4Jのバイナリサポートを見てみてください。

※注意点
クラスパスにバインディングが見つからない場合、SLF4J はデフォルトで動作しない実装になる。

まとめ

参考にした記事(いつもありがとうございます。)

SLF4J、Logback、Log4Jの関係を挙動とともに整理する

Simple Logging Facade for Java (SLF4J)

Frequently Asked Questions about SLF4J

javaでのFacadeパターン

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む