1. 序章

Micrometer は、多くの一般的な監視システムの計測クライアント上にシンプルなファサードを提供します。現在、次の監視システムをサポートしています:Atlas、Datadog、Graphite、Ganglia、Influx、JMX、とプロメテウス。

このチュートリアルでは、Micrometerの基本的な使用法とSpringとの統合について紹介します。

簡単にするために、ほとんどのユースケースを示す例としてMicrometerAtlasを取り上げます。

2. Mavenの依存関係

まず、pom.xmlに次の依存関係を追加しましょう。

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-atlas</artifactId>
    <version>1.7.1</version>
</dependency>

最新バージョンはここにあります。

3. MeterRegistry

マイクロメータでは、MeterRegistryがメータの登録に使用されるコアコンポーネントです。 レジストリとさらに各メーターのメトリックを反復処理して、メトリックとそのディメンション値の組み合わせを使用してバックエンドで時系列を生成できます。

レジストリの最も単純な形式はSimpleMeterRegistryです。 ただし、ほとんどの場合、監視システム用に明示的に設計されたMeterRegistryを使用する必要があります。 Atlasの場合は、AtlasMeterRegistryです。

CompositeMeterRegistry を使用すると、複数のレジストリを追加できます。 これは、サポートされているさまざまな監視システムにアプリケーションメトリックを同時に公開するためのソリューションを提供します。

データを複数のプラットフォームにアップロードするために必要なMeterRegistryを追加できます。

CompositeMeterRegistry compositeRegistry = new CompositeMeterRegistry();
SimpleMeterRegistry oneSimpleMeter = new SimpleMeterRegistry();
AtlasMeterRegistry atlasMeterRegistry 
  = new AtlasMeterRegistry(atlasConfig, Clock.SYSTEM);

compositeRegistry.add(oneSimpleMeter);
compositeRegistry.add(atlasMeterRegistry);

Micrometer、Metrics.globalRegistryには静的なグローバルレジストリサポートがあります。 また、このグローバルレジストリに基づく静的ビルダーのセットは、メトリックでメーターを生成するために提供されています。

@Test
public void givenGlobalRegistry_whenIncrementAnywhere_thenCounted() {
    class CountedObject {
        private CountedObject() {
            Metrics.counter("objects.instance").increment(1.0);
        }
    }
    Metrics.addRegistry(new SimpleMeterRegistry());

    Metrics.counter("objects.instance").increment();
    new CountedObject();

    Optional<Counter> counterOptional = Optional.ofNullable(Metrics.globalRegistry
      .find("objects.instance").counter());
    assertTrue(counterOptional.isPresent());
    assertTrue(counterOptional.get().count() == 2.0);
}

4. タグおよびメーター

4.1. タグ

Meter の識別子は、名前とタグで構成されます。 複数の監視システム間でのメトリック名の移植性を保証するために、単語をドットで区切る命名規則に従う必要があります。

Counter counter = registry.counter("page.visitors", "age", "20s");

タグは、値について推論するためにメトリックをスライスするために使用できます。 上記のコードでは、 page.visitors がメーターの名前であり、 age =20sがタグになっています。 この場合、カウンターは20〜30歳のページへの訪問者をカウントしています。

大規模なシステムの場合、レジストリに共通のタグを追加できます。 たとえば、メトリックが特定の地域からのものであるとします。

registry.config().commonTags("region", "ua-east");

4.2. カウンター

Counter は、アプリケーションの指定されたプロパティのカウントを報告するだけです。 流暢なビルダーまたは任意のMetricRegistryのヘルパーメソッドを使用してカスタムカウンターを作成できます。

Counter counter = Counter
  .builder("instance")
  .description("indicates instance count of the object")
  .tags("dev", "performance")
  .register(registry);

counter.increment(2.0);
 
assertTrue(counter.count() == 2);
 
counter.increment(-1);
 
assertTrue(counter.count() == 1);

上記のスニペットに見られるように、カウンターを1つ減らしようとしましたが、カウンターは固定された正の量だけ単調に増やすことができます。

4.3. タイマー

システム内のイベントの遅延または頻度を測定するには、Timersを使用できます。 Timer は、少なくとも特定の時系列の合計時間とイベント数を報告します。

たとえば、数秒続く可能性のあるアプリケーションイベントを記録できます。

SimpleMeterRegistry registry = new SimpleMeterRegistry();
Timer timer = registry.timer("app.event");
timer.record(() -> {
    try {
        TimeUnit.MILLISECONDS.sleep(15);
    } catch (InterruptedException ignored) {
    }
    });

timer.record(30, TimeUnit.MILLISECONDS);

assertTrue(2 == timer.count());
assertThat(timer.totalTime(TimeUnit.MILLISECONDS)).isBetween(40.0, 55.0);

長時間実行されるイベントを記録するには、LongTaskTimerを使用します。

SimpleMeterRegistry registry = new SimpleMeterRegistry();
LongTaskTimer longTaskTimer = LongTaskTimer
  .builder("3rdPartyService")
  .register(registry);

LongTaskTimer.Sample currentTaskId = longTaskTimer.start();
try {
    TimeUnit.MILLISECONDS.sleep(2);
} catch (InterruptedException ignored) { }
long timeElapsed = currentTaskId.stop();
 
 assertEquals(2L, timeElapsed/((int) 1e6),1L);

4.4. ゲージ

ゲージはメーターの現在の値を示します。

他のメーターとは異なり、ゲージは観測された場合にのみデータを報告する必要があります。 ゲージは、キャッシュまたはコレクションの統計を監視するときに役立ちます。

SimpleMeterRegistry registry = new SimpleMeterRegistry();
List<String> list = new ArrayList<>(4);

Gauge gauge = Gauge
  .builder("cache.size", list, List::size)
  .register(registry);

assertTrue(gauge.value() == 0.0);
 
list.add("1");
 
assertTrue(gauge.value() == 1.0);

4.5. DistributionSummary

イベントの配布と簡単な要約は、DistributionSummaryによって提供されます。

SimpleMeterRegistry registry = new SimpleMeterRegistry();
DistributionSummary distributionSummary = DistributionSummary
  .builder("request.size")
  .baseUnit("bytes")
  .register(registry);

distributionSummary.record(3);
distributionSummary.record(4);
distributionSummary.record(5);

assertTrue(3 == distributionSummary.count());
assertTrue(12 == distributionSummary.totalAmount());

さらに、DistributionSummaryおよびTimersは、パーセンタイルで強化できます。

SimpleMeterRegistry registry = new SimpleMeterRegistry();
Timer timer = Timer
  .builder("test.timer")
  .publishPercentiles(0.3, 0.5, 0.95)
  .publishPercentileHistogram()
  .register(registry);

上記のスニペットでは、タグ Percentile = 0.3 Percentile = 0.5、 Percentile =の3つのゲージがあります。 0.95 がレジストリで利用可能になり、95 %、50 %、および30% ofの観測値がそれぞれ下がる値を示します。

したがって、これらのパーセンタイルの動作を確認するために、いくつかのレコードを追加しましょう。

timer.record(2, TimeUnit.SECONDS);
timer.record(2, TimeUnit.SECONDS);
timer.record(3, TimeUnit.SECONDS);
timer.record(4, TimeUnit.SECONDS);
timer.record(8, TimeUnit.SECONDS);
timer.record(13, TimeUnit.SECONDS);

次に、これらの3パーセンタイルゲージの値を抽出して確認できます。

Map<Double, Double> actualMicrometer = new TreeMap<>();
ValueAtPercentile[] percentiles = timer.takeSnapshot().percentileValues();
for (ValueAtPercentile percentile : percentiles) {
    actualMicrometer.put(percentile.percentile(), percentile.value(TimeUnit.MILLISECONDS));
}

Map<Double, Double> expectedMicrometer = new TreeMap<>();
expectedMicrometer.put(0.3, 1946.157056);
expectedMicrometer.put(0.5, 3019.89888);
expectedMicrometer.put(0.95, 13354.663936);

assertEquals(expectedMicrometer, actualMicrometer);

さらに、Micrometerはサービスレベル目標(ヒストグラム)もサポートしています。

DistributionSummary hist = DistributionSummary
  .builder("summary")
  .serviceLevelObjectives(1, 10, 5)
  .register(registry);

パーセンタイルと同様に、いくつかのレコードを追加した後、ヒストグラムが計算を非常にうまく処理していることがわかります。

Map<Integer, Double> actualMicrometer = new TreeMap<>();
HistogramSnapshot snapshot = hist.takeSnapshot();
Arrays.stream(snapshot.histogramCounts()).forEach(p -> {
  actualMicrometer.put((Integer.valueOf((int) p.bucket())), p.count());
});

Map<Integer, Double> expectedMicrometer = new TreeMap<>();
expectedMicrometer.put(1,0D);
expectedMicrometer.put(10,2D);
expectedMicrometer.put(5,1D);

assertEquals(expectedMicrometer, actualMicrometer);

一般に、ヒストグラムは、個別のバケットでの直接比較を説明するのに役立ちます。 ヒストグラムは時間スケールにすることもできます。これは、バックエンドサービスの応答時間を分析するのに非常に役立ちます。

Duration[] durations = {Duration.ofMillis(25), Duration.ofMillis(300), Duration.ofMillis(600)};
Timer timer = Timer
  .builder("timer")
  .sla(durations)
  .publishPercentileHistogram()
  .register(registry);

5. バインダー

Micrometerには、JVM、キャッシュ、 ExecutorService、、およびロギングサービスを監視するための複数の組み込みバインダーがあります。

JVMとシステムの監視に関しては、クラスローダーメトリック( ClassLoaderMetrics )、JVMメモリプール( JvmMemoryMetrics )、およびGCメトリック( JvmGcMetrics )を監視できます。スレッドとCPUの使用率( JvmThreadMetrics ProcessorMetrics )。

キャッシュモニタリング(現在、Guava、EhCache、Hazelcast、およびCaffeineのみがサポートされています)は、 GuavaCacheMetrics EhCache2Metrics HazelcastCacheMetrics 、およびを使用してインストルメント化することでサポートされます。 CaffeineCacheMetrics。 また、ログバックサービスを監視するために、LogbackMetricsを任意の有効なレジストリにバインドできます。

new LogbackMetrics().bind(registry);

上記のバインダーの使用法は、 LogbackMetrics、と非常によく似ており、すべてかなり単純なので、ここではこれ以上詳しく説明しません。

6. 春の統合

Spring Bootアクチュエータは、Micrometerの依存関係管理と自動構成を提供します。 Spring Boot 2.0/1.xおよびSpringFramework5.0/4.xでサポートされるようになりました。

次の依存関係が必要になります(最新バージョンはここにあります):

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-spring-legacy</artifactId>
    <version>1.3.20</version>
</dependency>

既存のコードをさらに変更することなく、MicrometerでSpringサポートを有効にしました。 SpringアプリケーションのJVMメモリメトリックは、グローバルレジストリに自動的に登録され、デフォルトのアトラスエンドポイント http:// localhost:7101 / api / v1 /publishに公開されます。

spring.metrics.atlas。*で始まる、メトリックのエクスポート動作を制御するために使用できるいくつかの構成可能なプロパティがあります。 AtlasConfig をチェックして、Atlasパブリッシングの構成プロパティの完全なリストを確認してください。

さらにメトリックをバインドする必要がある場合は、それらを@Beanとしてアプリケーションコンテキストに追加するだけです。

JvmThreadMetricsが必要だとします。

@Bean
JvmThreadMetrics threadMetrics(){
    return new JvmThreadMetrics();
}

Webモニタリングに関しては、アプリケーション内のすべてのエンドポイントに対して自動構成されますが、構成プロパティ spring.metrics.web.autoTimeServerRequestsを介して管理できます。

デフォルトの実装では、エンドポイントのメトリックの4つのディメンション(HTTP要求メソッド、HTTP応答コード、エンドポイントURI、および例外情報)が提供されます。

リクエストが応答されると、リクエストメソッド( GET POST など)に関連するメトリックがAtlasで公開されます。

Atlas Graph API を使用すると、さまざまなメソッドの応答時間を比較するためのグラフを生成できます。

デフォルトでは、 20x 30x 40x 50xの応答コードも報告されます。

さまざまなURIを比較することもできます。

または、例外メトリックを確認します。

コントローラクラスまたは特定のエンドポイントメソッドで@Timedを使用して、メトリックのタグ、長いタスク、分位数、およびパーセンタイルをカスタマイズすることもできることに注意してください。

@RestController
@Timed("people")
public class PeopleController {

    @GetMapping("/people")
    @Timed(value = "people.all", longTask = true)
    public List<String> listPeople() {
        //...
    }

}

上記のコードに基づいて、Atlasエンドポイント http:// localhost:7101 / api / v1 / tags / name をチェックすると、次のタグを確認できます。

["people", "people.all", "jvmBufferCount", ... ]

Micrometerは、SpringBoot2.0で導入された関数Webフレームワークでも機能します。 RouterFunction をフィルタリングすることで、メトリックを有効にできます。

RouterFunctionMetrics metrics = new RouterFunctionMetrics(registry);
RouterFunctions.route(...)
  .filter(metrics.timer("server.requests"));

データソースとスケジュールされたタスクからメトリックを収集することもできます。 詳細については、公式ドキュメントを確認してください。

7. 結論

この記事では、メトリックファサードマイクロメータを紹介しました。 このツールは、共通のセマンティクスの下で複数の監視システムを抽象化してサポートすることにより、異なる監視プラットフォーム間の切り替えを非常に簡単にします。

いつものように、この記事の完全な実装コードは、Githubにあります。