最新のSpringBoot3系についてはこちらの記事でまとめました!
概要
- springbootのアプリケーションのメトリクス監視をしたい
- http.client.requests
- APIのレスポンスタイムはどれくらいなのか?
- APIのリクエスト数はどれくらいなのか?
- http.server.requests
- 内部で使用しているAPIのレスポンスはどれくらいなのか
- http.client.requests
- 取得したメトリクスはprometheusやgrafanaで可視化!
- ※本記事では触れません
使うライブラリ
※抜粋
buildscript {
ext {
springBootVersion = '2.1.1.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-actuator')
compile('org.springframework.boot:spring-boot-actuator-autoconfigure')
compile('io.micrometer:micrometer-registry-prometheus')
}
-
spring-boot-starter-web
- メトリクスの取得には関係なし、メトリクス取得対象のAPI作成のために使用
-
spring-boot-actuator
- メトリクスを取得するための機能はこの中に入っている
-
spring-boot-actuator-autoconfigure
-
spring-boot-actuator
が提供している機能をDIコンテナに登録する役割
-
-
micrometer-registry-prometheus
- 取得したメトリクスをprometheusから読み取れる形式に変換してくれる
監視対象のアプリケーション
「Hello World!」と返すだけのAPI
@Controller
@SpringBootApplication
@Slf4j
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@GetMapping
@ResponseBody
public String hello() {
return "Hello World!";
}
}
http.server.requests
まずはアプリケーション自身のメトリクス取得!
実装
実装上必要なのは
- application.ymlに設定を記載(もちろんapplication.propertiesでも可)
だけ!!!
management:
endpoints:
web:
exposure:
include: "prometheus"
base-path: "/"
server:
port: 9990
metrics:
web:
server:
requests-metric-name: "http.server.requests"
distribution:
percentiles:
"http.server.requests": 0.5,0.99
- この設定をしておくとlocalhost:9990/prometheusにアクセスするとメトリクスが取得できる!
-
management.metrics
配下の設定値はなくても動きます-
requests-metric-name
はメトリクスの名前を任意のものに変更するための設定値です -
percentiles
を設定しておくとパーセンタイルが取得できます- この設定だと50%ileと99%ile
-
いざ、bootRun!
何回かアクセス(curl localhost:8080
)
メトリクス取得!
$ curl localhost:9990/prometheus
# TYPE http_server_requests_seconds summary
http_server_requests_seconds{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/",quantile="0.5",} 0.0
http_server_requests_seconds{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/",quantile="0.99",} 0.0
http_server_requests_seconds_count{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/",} 7.0
http_server_requests_seconds_sum{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/",} 0.086270892
めでたい!
誰が何をしているの?
メトリクスを取ってくれているのはorg.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter
。
主な処理があつまっているがこのメソッドかな
private void filterAndRecordMetrics(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
TimingContext timingContext = TimingContext.get(request);
if (timingContext == null) {
timingContext = startAndAttachTimingContext(request);
}
try {
filterChain.doFilter(request, response);
if (!request.isAsyncStarted()) {
// Only record when async processing has finished or never been started.
// If async was started by something further down the chain we wait
// until the second filter invocation (but we'll be using the
// TimingContext that was attached to the first)
Throwable exception = (Throwable) request
.getAttribute(DispatcherServlet.EXCEPTION_ATTRIBUTE);
record(timingContext, response, request, exception);
}
}
catch (NestedServletException ex) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
record(timingContext, response, request, ex.getCause());
throw ex;
}
}
このクラスはOncePerRequestFilter
を継承しているから
Bean登録さえしてあればメトリクスを取ってくれるのね!
Bean登録は?
僕はよくbeans
エンドポイントを有効にして調べちゃいます
management:
endpoints:
web:
exposure:
include: "prometheus,beans"
base-path: "/"
server:
port: 9990
prometheusに追加してbeansを追加すると
localhost:9990/beansでbean登録されている一覧がとれる
webMvcMetricsFilter: {
aliases: [ ],
scope: "singleton",
type: "org.springframework.boot.web.servlet.FilterRegistrationBean",
resource: "class path resource
[org/springframework/boot/actuate/autoconfigure/metrics/web/servlet/WebMvcMetricsAutoConfiguration.class]",
dependencies: [
"prometheusMeterRegistry",
"webMvcTagsProvider"
]
},
ふむふむ、WebMvcMetricsAutoConfiguration
か
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@ConditionalOnBean(MeterRegistry.class)
使用している2.1.1
だとこんなconditionalだから自動でメトリクス集計してくれているみたい
(逆にライブラリを入れるとメトリクス集計しちゃうから不要だったらライブラリいれちゃダメかも、当たり前か。。)
http.client.requests
実装
- 意識しなきゃいけないのは
RestTemplate
- RestTemplateBuilderから直接buildするときは特に意識する必要なし
- 独自のbuilderを使いたいときを想定して実装
- customizersメソッドにBean登録されているMetricsRestTemplateCustomizerを渡すだけ
- 何も記載しなくてもBean登録はされてるけど、一応99%ile取得を想定してapplication.ymlも修正
management:
endpoints:
web:
exposure:
include: "prometheus,beans"
base-path: "/"
server:
port: 9990
metrics:
web:
server:
requests-metric-name: "http.server.requests"
client:
requests-metric-name: "http.client.requests"
distribution:
percentiles:
"http.server.requests": 0.5,0.99
"http.client.requests": 0.5,0.99
@Controller
@SpringBootApplication
@Slf4j
public class Application {
private RestTemplate restTemplate;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@GetMapping
@ResponseBody
public String hello() {
return restTemplate.getForObject(URI.create("http://localhost:8080/hello"), String.class);
}
public Application(
RestTemplateBuilder restTemplateBuilder,
MetricsRestTemplateCustomizer metricsRestTemplateCustomizer
) {
// デフォルトのRestTemplateBuilderを使う場合は意識する必要ない
// this.restTemplate = restTemplateBuilder
// .build();
this.restTemplate = restTemplateBuilder
.customizers(metricsRestTemplateCustomizer)
.build();
}
@GetMapping("/hello")
@ResponseBody
public String helloApi() {
return "Hello World!";
}
}
こんな感じでメトリクスが取れる
$ curl http://localhost:9990/prometheus
# TYPE http_client_requests_seconds summary
http_client_requests_seconds{clientName="localhost",method="GET",status="200",uri="/hello",quantile="0.5",} 0.06291456
http_client_requests_seconds{clientName="localhost",method="GET",status="200",uri="/hello",quantile="0.99",} 0.06291456
http_client_requests_seconds_count{clientName="localhost",method="GET",status="200",uri="/hello",} 1.0
http_client_requests_seconds_sum{clientName="localhost",method="GET",status="200",uri="/hello",} 0.064232646
誰が何をしているの?
- メトリクスを取ってくれているのは
org.springframework.boot.actuate.metrics.web.client.MetricsClientHttpRequestInterceptor
。 - このクラスは先ほどの実装で登録したcustomizer(
MetricsRestTemplateCustomizer
)が使用している -
MetricsRestTemplateCustomizer
はRestTemplateMetricsConfiguration
によってBean登録されている - デフォルトの(Bean登録されている)RestTemplateBuilderを使う場合は、
RestTemplateBuilder
のコンストラクタの引数がRestTemplateCustomizerの配列になっているから、登録されるCustomizerを全部拾ってくれるのね! - conditionalはRestTemplateを見ているからRestTemplateを使っていたらcustomizerはBean登録されているのね!
@ConditionalOnClass(RestTemplate.class)
さいごに
99%ileが簡単に取れてビックリ
作った人すごい。。。