1. 序章

クラウドネイティブアプリケーションとマイクロサービスの人気の高まりにより、組み込みサーブレットコンテナの需要が高まっています。 Spring Bootを使用すると、開発者は、Tomcat、Undertow、Jettyの3つの最も成熟したコンテナーを使用してアプリケーションやサービスを簡単に構築できます。

このチュートリアルでは、起動時および負荷がかかった状態で取得されたメトリックを使用して、コンテナーの実装をすばやく比較する方法を示します。

2. 依存関係

使用可能な各コンテナー実装のセットアップでは、pom.xmlspring-boot-starter-webへの依存関係を宣言する必要があります。

一般に、親を spring-boot-starter-parent として指定してから、必要なスターターを含めます。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.0</version>
    <relativePath/>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

2.1. Tomcat

spring-boot-starter-web を使用するとデフォルトで含まれるため、Tomcatを使用する場合はこれ以上の依存関係は必要ありません。

2.2. 桟橋

Jettyを使用するには、最初にspring-boot-starter-tomcatspring-boot-starter-webから除外する必要があります。

次に、spring-boot-starter-jettyへの依存関係を宣言するだけです。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

2.3. 逆流

Undertowの設定は、依存関係として spring-boot-starter-undertow を使用することを除いて、Jettyと同じです。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

2.4. アクチュエータ

Spring Bootのアクチュエータを、システムにストレスを与え、メトリックを照会するための便利な方法として使用します。

アクチュエータの詳細については、この記事をご覧ください。 pom に依存関係を追加するだけで、次のように利用できるようになります。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

 2.5. Apacheベンチ

Apache Bench は、ApacheWebサーバーにバンドルされているオープンソースの負荷テストユーティリティです。

Windowsユーザーは、ここにリンクされているサードパーティベンダーの1つからApacheをダウンロードできます。 ApacheがすでにWindowsマシンにインストールされている場合は、 apache /binディレクトリでab.exeを見つけることができるはずです。

Linuxマシンを使用している場合、 ab は、apt-getを使用してインストールできます。

$ apt-get install apache2-utils

3. スタートアップメトリクス

3.1. コレクション

起動メトリックを収集するために、Spring BootのApplicationReadyEventで起動するイベントハンドラーを登録します。

Actuatorコンポーネントで使用されるMeterRegistryを直接操作して、関心のあるメトリックをプログラムで抽出します。

@Component
public class StartupEventHandler {

    // logger, constructor
    
    private String[] METRICS = {
      "jvm.memory.used", 
      "jvm.classes.loaded", 
      "jvm.threads.live"};
    private String METRIC_MSG_FORMAT = "Startup Metric >> {}={}";
    
    private MeterRegistry meterRegistry;

    @EventListener
    public void getAndLogStartupMetrics(
      ApplicationReadyEvent event) {
        Arrays.asList(METRICS)
          .forEach(this::getAndLogActuatorMetric);
    }

    private void processMetric(String metric) {
        Meter meter = meterRegistry.find(metric).meter();
        Map<Statistic, Double> stats = getSamples(meter);
 
        logger.info(METRIC_MSG_FORMAT, metric, stats.get(Statistic.VALUE).longValue());
    }

    // other methods
}

イベントハンドラー内の起動時に興味深いメトリックをログに記録することで、アクチュエーターRESTエンドポイントを手動でクエリしたり、スタンドアロンのJMXコンソールを実行したりする必要がなくなります。

3.2. 選択

Actuatorがすぐに提供するメトリックは多数あります。 サーバーが起動した後、主要なランタイム特性の概要を把握するのに役立つ3つのメトリックを選択しました。

  • jvm.memory.used –起動以降にJVMによって使用された合計メモリ
  • jvm.classes.loaded –ロードされたクラスの総数
  • jvm.threads.live –アクティブなスレッドの総数。 私たちのテストでは、この値は「静止時」のスレッド数と見なすことができます。

4. ランタイムメトリクス

4.1. コレクション

起動メトリックを提供することに加えて、アプリケーションに負荷をかけるために、Apache Benchを実行するときに、アクチュエータによって公開される/metricsエンドポイントをターゲットURLとして使用します。

負荷がかかった状態で実際のアプリケーションをテストするために、代わりにアプリケーションが提供するエンドポイントを使用する場合があります。

サーバーが起動すると、コマンドプロンプトが表示され、abが実行されます。

ab -n 10000 -c 10 http://localhost:8080/actuator/metrics

上記のコマンドでは、10個の同時スレッドを使用して合計10,000個のリクエストを指定しました。

4.2. 選択

Apache Benchは、接続時間や特定の時間内に処理されたリクエストの割合など、いくつかの有用な情報を非常に迅速に提供することができます。

私たちの目的のために、 1秒あたりのリクエスト数とリクエストあたりの時間(平均)に焦点を当てました。

5. 結果

起動時に、 Tomcat、Jetty、およびUndertowのメモリフットプリントは同等であり、Undertowは他の2つよりもわずかに多くのメモリを必要とし、Jettyは最小量を必要とすることがわかりました。

私たちのベンチマークでは、 Tomcat、Jetty、およびUndertowのパフォーマンスは同等でしたでもあの Undertowは明らかに最速で、Jettyはわずかに低速でした。 

メトリック Tomcat 桟橋 逆流
jvm.memory.used(MB) 168 155 164
jvm.classes.loaded 9869 9784 9787
jvm.threads.live 25 17 19
1秒あたりのリクエスト数 1542 1627 1650
リクエストあたりの平均時間(ミリ秒) 6.483 6.148 6.059

メトリックは、当然、必要最低限のプロジェクトを表すことに注意してください。 独自のアプリケーションのメトリックは、間違いなく異なります。

6. ベンチマークディスカッション

サーバー実装の徹底的な比較を実行するための適切なベンチマークテストの開発は、複雑になる可能性があります。 最も関連性の高い情報を抽出するには、問題のユースケースにとって何が重要であるかを明確に理解することが重要です

この例で収集されたベンチマーク測定値は、アクチュエータエンドポイントへのHTTPGETリクエストで構成される非常に特殊なワークロードを使用して取得されたことに注意することが重要です。

ワークロードが異なると、コンテナの実装全体で相対的な測定値が異なる可能性が高いと予想されます。 より堅牢で正確な測定が必要な場合は、実稼働のユースケースにより厳密に一致するテスト計画を設定することをお勧めします。

さらに、JMeterGatlingなどのより洗練されたベンチマークソリューションは、より価値のある洞察をもたらす可能性があります。

7. コンテナの選択

適切なコンテナー実装の選択は、少数のメトリックだけではきちんと要約できない多くの要因に基づいている可能性があります。 快適さのレベル、機能、利用可能な構成オプション、およびポリシーは、それほど重要ではないにしても、同じように重要であることがよくあります。

8. 結論

この記事では、Tomcat、Jetty、およびUndertowの組み込みサーブレットコンテナの実装について説明しました。 Actuatorコンポーネントによって公開されたメトリックを調べることにより、デフォルト構成での起動時の各コンテナーの実行時特性を調べました。

実行中のシステムに対して不自然なワークロードを実行し、ApacheBenchを使用してパフォーマンスを測定しました。

最後に、この戦略のメリットについて説明し、実装ベンチマークを比較する際に留意すべきいくつかの事項について説明しました。 いつものように、すべてのソースコードはGitHubにあります。