Exponer las métricas de Hystrix en Grafana con Prometheus de una aplicación Spring Boot

Escrito por picodotdev el .
java planeta-codigo programacion software
Comentarios

Java
Spring

Hystrix es una implementación del patrón circuit breaker para hacer que un servicio sea tolerante fallos cuando aquellos que utiliza fallan. Es conveniente tener una herramienta de monitorización para conocer el estado del sistema y actuar pronto o conocer si el comportamiento del sistema es diferente al hacer algún cambio. Hystrix proporciona varios datos como el número de peticiones realizadas, cuantas han fallado o cual es el estado del patrón circuit breaker. Prometheus es una herramienta de monitorización que recoge las métricas de los servicios de forma periódica y las almacena para una consulta posterior, Grafana es otra herramienta de monitorización que permite visualizar en gráficas las métricas almacenadas en Prometheus y observar los valores a lo largo del tiempo.

En el artículo Tolerancia a fallos en un cliente de microservicio con Spring Cloud Netflix y Hystrix explicaba como crear un servicio de Spring que implementa el patrón circuit breaker con Hystrix y en el artículo Monitorizar una aplicación Java con Spring Boot, Micrometer, Prometheus y Grafana explicaba como exportar las métricas de Spring Boot Actuator a Prometheus y como crear gráficas en Grafana.

Hystrix ofrece un dashboard algo espartano con los datos de Hystrix de la propia aplicación. Los datos de las métricas de Hystrix por defecto no se exponen en Spring Boot Actuator pero se pueden añadir creando un bean HystrixMetricsBinder en la configuración de Spring.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package io.github.picodotdev.blogbitix.springcloud.client;

...

@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
@EnableHystrixDashboard
public class Main implements CommandLineRunner {

  ...

    @Bean
    HystrixMetricsBinder hystrixMetricsBinder() {
        return new HystrixMetricsBinder();
    }
    
  ...

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(Main.class);
        application.setApplicationContextClass(AnnotationConfigApplicationContext.class);
        SpringApplication.run(Main.class, args);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
...

dependencies {
    // Spring
    def excludeSpringBootStarterLogging = { exclude(group: 'org.springframework.boot', module: 'spring-boot-starter-logging') }
    compile('org.springframework.boot:spring-boot-starter', excludeSpringBootStarterLogging)
    compile('org.springframework.boot:spring-boot-starter-web', excludeSpringBootStarterLogging)
    compile('org.springframework.boot:spring-boot-starter-log4j2', excludeSpringBootStarterLogging)
    compile('org.springframework.boot:spring-boot-starter-actuator', excludeSpringBootStarterLogging)
    compile('org.springframework.cloud:spring-cloud-starter-config', excludeSpringBootStarterLogging)
    compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client', excludeSpringBootStarterLogging)
    compile('org.springframework.cloud:spring-cloud-starter-netflix-ribbon', excludeSpringBootStarterLogging)
    compile('org.springframework.cloud:spring-cloud-starter-netflix-hystrix', excludeSpringBootStarterLogging)
    compile('org.springframework.cloud:spring-cloud-starter-netflix-hystrix-dashboard', excludeSpringBootStarterLogging)
    compile('io.micrometer:micrometer-registry-prometheus:1.0.8')

    ...
}

Una vez hecho esto Spring en el endpoint /actuator/metrics se exponen las métricas de Hystrix, si además se configura Spring añadiendo la dependencia io.micrometer:micrometer-registry-prometheus para exponer las métricas en el formato para que Prometheus las recolecta también se añaden en el endpoint /actuator/prometheus.

1
2
3
4
5
6
7
8
9
$ curl http://client.127.0.0.1.xip.io:8095/actuator/metrics
$ curl http://client.127.0.0.1.xip.io:8095/actuator/metrics/hystrix.requests
$ curl http://client.127.0.0.1.xip.io:8095/actuator/metrics/hystrix.circuit.breaker.open
$ curl http://client.127.0.0.1.xip.io:8095/actuator/metrics/hystrix.fallback
$ http://client.127.0.0.1.xip.io:8095/actuator/metrics/hystrix.latency.total
$ http://client.127.0.0.1.xip.io:8095/actuator/metrics/hystrix.errors

$ curl http://client.127.0.0.1.xip.io:8095/actuator/prometheus
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{
  "names": [
    ...
    "hystrix.requests",
    "hystrix.command.other",
    "hystrix.circuit.breaker.open",
    "hystrix.fallback",
    "hystrix.latency.execution",
    "hystrix.execution",
    "hystrix.latency.total",
    "hystrix.threadpool.concurrent.execution.rolling.max",
    "hystrix.errors",
    "hystrix.threadpool.concurrent.execution.current",
    ...
  ]
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
...
# HELP hystrix_threadpool_concurrent_execution_current  
# TYPE hystrix_threadpool_concurrent_execution_current gauge
hystrix_threadpool_concurrent_execution_current{group="ClientService",key="get",threadpool="ClientService",} 0.0
# HELP hystrix_requests_total  
# TYPE hystrix_requests_total counter
hystrix_requests_total{group="ClientService",key="get",} 137.0
# HELP hystrix_circuit_breaker_open  
# TYPE hystrix_circuit_breaker_open gauge
hystrix_circuit_breaker_open{group="ClientService",key="get",} 0.0
# HELP hystrix_latency_execution_seconds_max  
# TYPE hystrix_latency_execution_seconds_max gauge
hystrix_latency_execution_seconds_max{group="ClientService",key="get",} 0.472
# HELP hystrix_latency_execution_seconds  
# TYPE hystrix_latency_execution_seconds summary
hystrix_latency_execution_seconds_count{group="ClientService",key="get",} 137.0
hystrix_latency_execution_seconds_sum{group="ClientService",key="get",} 2.839
# HELP hystrix_execution_total Execution results. See https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring#command-execution-event-types-comnetflixhystrixhystrixeventtype for type definitions
# TYPE hystrix_execution_total counter
hystrix_execution_total{event="emit",group="ClientService",key="get",} 0.0
hystrix_execution_total{event="success",group="ClientService",key="get",} 137.0
hystrix_execution_total{event="failure",group="ClientService",key="get",} 0.0
hystrix_execution_total{event="timeout",group="ClientService",key="get",} 0.0
hystrix_execution_total{event="bad_request",group="ClientService",key="get",} 0.0
hystrix_execution_total{event="short_circuited",group="ClientService",key="get",} 0.0
hystrix_execution_total{event="thread_pool_rejected",group="ClientService",key="get",} 0.0
hystrix_execution_total{event="semaphore_rejected",group="ClientService",key="get",} 0.0
# HELP hystrix_threadpool_concurrent_execution_rolling_max  
# TYPE hystrix_threadpool_concurrent_execution_rolling_max gauge
hystrix_threadpool_concurrent_execution_rolling_max{group="ClientService",key="get",threadpool="ClientService",} 0.0
# HELP hystrix_command_other_total Other execution results. See https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring#other-command-event-types-comnetflixhystrixhystrixeventtype for type definitions
# TYPE hystrix_command_other_total counter
hystrix_command_other_total{event="exception_thrown",group="ClientService",key="get",} 0.0
hystrix_command_other_total{event="response_from_cache",group="ClientService",key="get",} 0.0
hystrix_command_other_total{event="cancelled",group="ClientService",key="get",} 0.0
hystrix_command_other_total{event="collapsed",group="ClientService",key="get",} 0.0
hystrix_command_other_total{event="command_max_active",group="ClientService",key="get",} 0.0
# HELP hystrix_latency_total_seconds_max  
# TYPE hystrix_latency_total_seconds_max gauge
hystrix_latency_total_seconds_max{group="ClientService",key="get",} 0.474
# HELP hystrix_latency_total_seconds  
# TYPE hystrix_latency_total_seconds summary
hystrix_latency_total_seconds_count{group="ClientService",key="get",} 137.0
hystrix_latency_total_seconds_sum{group="ClientService",key="get",} 2.857
# HELP hystrix_errors_total  
# TYPE hystrix_errors_total counter
hystrix_errors_total{group="ClientService",key="get",} 0.0
# HELP hystrix_fallback_total Fallback execution results. See https://github.com/Netflix/Hystrix/wiki/Metrics-and-Monitoring#command-fallback-event-types-comnetflixhystrixhystrixeventtype for type definitions
# TYPE hystrix_fallback_total counter
hystrix_fallback_total{event="fallback_emit",group="ClientService",key="get",} 0.0
hystrix_fallback_total{event="fallback_success",group="ClientService",key="get",} 0.0
hystrix_fallback_total{event="fallback_failure",group="ClientService",key="get",} 0.0
hystrix_fallback_total{event="fallback_rejection",group="ClientService",key="get",} 0.0
hystrix_fallback_total{event="fallback_missing",group="ClientService",key="get",} 0.0
...

Con estas métricas recolectadas por Prometheus se pueden visualizar en gráficas por Grafana. Hay algunos paneles de Grafana para Hystrix como el 7145 pero que necesitan ser adaptados según la nomenclatura de las propiedades expuestas por Spring Boot. En este caso se monitoriza el número de peticiones realizadas, el tiempo de latencia, si los circuitos están abiertos, los fallos, éxitos y tiemouts así como el estado de los thread pools que utiliza Hystrix para realizar las peticiones de un cliente a un servicio.

Panel de Grafana para métricas de Hystrix

Exponer las métricas en una aplicación de Spring Boot para Prometheus es muy sencillo y con Grafana se puede observar el estado del sistema de forma tan detallada como lo sean las métricas expuestas por la aplicación. Por defecto Spring Boot ya expone una buena cantidad de métricas del estado del servicio como uso de CPU, memoria, hilos o recolector de basura.

1
2
3
4
$ ./gradlew discoveryserver:run --args="--port=8761"
$ ./gradlew configserver:run --args="--port=8090"
$ ./gradlew service:run --args="--port=8080"
$ ./gradlew client:run

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradle-run.sh.