Monitoreo de aplicaciones Java Spring con Prometheus y Grafana

Recientemente, algunos procesos comerciales deben monitorearse de extremo a extremo. Estos negocios están compuestos por varios microservicios. Todos los microservicios están escritos en Java Spring. Necesitamos comprender las estadísticas de tráfico y el estado de rendimiento de cada módulo involucrado en todo el negocio. , como un total de Cuántas llamadas de solicitud comercial, cuántas respuestas exitosas o fallidas, cuánto tiempo lleva cada paso, etc. Por lo tanto, también estudié cómo generar indicadores estadísticos en aplicaciones Java Spring, recopilar indicadores de manera uniforme a través de Prometheus y presentar esta información a través de diferentes informes en Grafana.

Primero, definamos un proceso comercial simple. Supongamos que tenemos dos aplicaciones Spring. Una es una llamada HTTP que proporciona una interfaz de solicitud comercial. Después de recibir la solicitud comercial, la información contenida en ella se envía a Kafka. Otra aplicación es suscribirse a los mensajes de Kafka, obtener los datos comerciales enviados por la Aplicación 1 y procesarlos.

aplicación uno

Cree una nueva aplicación en el sitio web start.spring.io, el nombre del artefacto es kafka-sender-example y seleccione Apache kafka para spring, Actuator, Spring Web en Dependencias. Abra el archivo de proyecto generado, agregue una clase llamada RemoteCommandController, implemente una interfaz http, el código es el siguiente:

package cn.roygao.kafkasenderexample;

import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;

import org.apache.kafka.clients.producer.ProducerRecord;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.fastjson.JSONObject;

@RestController
public class RemoteCommandController {
    @Autowired
    private KafkaTemplate<Integer, String> template;

    private final static Logger LOGGER = Logger.getLogger(RemoteCommandController.class.getName());

    @PostMapping("/sendcommand")
    public ResponseEntity<Map<String, Object>> sendCommand(@RequestBody JSONObject commandMsg) {
        String requestId = UUID.randomUUID().toString();
        String vin = commandMsg.getString("vin");
        String command = commandMsg.getString("command");
        LOGGER.info("Send command to vehicle:" + vin + ", command:" + command);
        Map<String, Object> requestIdObj = Collections.singletonMap("requestId", requestId);
        ProducerRecord<Integer, String> record = new ProducerRecord<>("remotecommand", 1, command);
        try {
            System.out.println(System.currentTimeMillis());
            template.send(record).get(10, TimeUnit.SECONDS);
        }
        catch (ExecutionException e) {
            LOGGER.info("Error");
            LOGGER.info(e.getMessage());
        }
        catch (TimeoutException | InterruptedException e) {
            LOGGER.info("Timeout");
            LOGGER.info(e.getMessage());
        }
        return ResponseEntity.accepted().body(requestIdObj);
    }
}

Este código es muy simple. Proporciona una interfaz POST/sendcommand. El usuario llama a esta interfaz y proporciona el número VIN del vehículo y la información del comando que se enviará. Después de recibir la solicitud, la información de la solicitud comercial se reenviará al mensaje. tema de Kafka. KafkaTemplate se usa aquí para enviar mensajes. Para hacer esto, defina una clase de configuración llamada KafkaSender con el siguiente código:

package cn.roygao.kafkasenderexample;

import java.util.HashMap;
import java.util.Map;

import org.apache.kafka.clients.admin.NewTopic;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.IntegerSerializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.config.TopicBuilder;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;

@Configuration
public class KafkaSender {
    @Bean
    public NewTopic topic() {
        return TopicBuilder.name("remotecommand")
                .build();
    }

    @Bean
    public ProducerFactory<Integer, String> producerFactory() {
        return new DefaultKafkaProducerFactory<>(producerConfigs());
    }

    @Bean
    public Map<String, Object> producerConfigs() {
        Map<String, Object> props = new HashMap<>();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, IntegerSerializer.class);
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        // See https://kafka.apache.org/documentation/#producerconfigs for more properties
        return props;
    }

    @Bean
    public KafkaTemplate<Integer, String> kafkaTemplate() {
        return new KafkaTemplate<Integer, String>(producerFactory());
    }
}

El código define la dirección del servidor Kafka, los temas de los mensajes y otras configuraciones.

Ejecute ./mvnw clean package para compilar y empaquetar.

aplicación dos

Cree una nueva aplicación en el sitio web start.spring.io, el nombre del artefacto es kafka-sender-example y seleccione Apache kafka para spring, Actuator in Dependencies. Abra el archivo de proyecto generado y cree una nueva clase llamada RemoteCommandHandler para realizar la función de recibir información de Kafka. El código es el siguiente:

package cn.roygao.kafkareceiverexample;

import java.util.concurrent.TimeUnit;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.listener.adapter.ConsumerRecordMetadata;
import org.springframework.stereotype.Component;

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;

@Component
public class RemoteCommandHandler {
    private Timer timer;

    public RemoteCommandHandler(MeterRegistry registry) {
        this.timer = Timer
            .builder("kafka.process.latency")
            .publishPercentiles(0.15, 0.5, 0.95)
            .publishPercentileHistogram()
            .register(registry);
    }

    @KafkaListener(id = "myId", topics = "remotecommand")
    public void listen(String in, ConsumerRecordMetadata meta) {
        long latency = System.currentTimeMillis()-meta.timestamp();
        timer.record(latency, TimeUnit.MILLISECONDS);
    }
}

El constructor de esta clase debe pasar un objeto MeterRetistry y luego crear un nuevo objeto Timer, que es una de las cuatro métricas proporcionadas por Micrometer y se puede usar para registrar información de duración. Registre este temporizador en MeterRegistry.

En el método de escucha, se define suscribirse al mensaje del tema del mensaje de Kafka, obtener la marca de tiempo de la hora de generación en los metadatos del mensaje y compararla con la hora actual, calcular el tiempo que lleva desde la generación del mensaje hasta el mensaje. consumo, y luego use el temporizador para calcular. Timer realizará estadísticas de distribución de diferentes intervalos percentiles según la definición anterior.

Del mismo modo, también necesitamos definir una clase de configuración de Kafka, el código es el siguiente:

package cn.roygao.kafkareceiverexample;

import java.util.HashMap;
import java.util.Map;

import org.apache.kafka.clients.producer.ProducerConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.config.KafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;

@Configuration
@EnableKafka
public class KafkaConfig {
    @Bean
    KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<Integer, String>>
                        kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<Integer, String> factory =
                                new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory());
        factory.setConcurrency(3);
        factory.getContainerProperties().setPollTimeout(3000);
        return factory;
    }

    @Bean
    public ConsumerFactory<Integer, String> consumerFactory() {
        return new DefaultKafkaConsumerFactory<>(consumerConfigs());
    }

    @Bean
    public Map<String, Object> consumerConfigs() {
        Map<String, Object> props = new HashMap<>();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.IntegerDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        return props;
    }
}

Agregue la siguiente configuración al archivo application.properties:

spring.kafka.consumer.auto-offset-reset=earliest
server.port=7777
management.endpoints.web.exposure.include=health,info,prometheus
management.endpoints.enabled-by-default=true
management.endpoint.health.show-details: always

Luego ejecute ./mvnw clean package para compilar y empaquetar.

Iniciar Kafka

Aquí uso Docker para iniciar Kafka. El contenido del archivo de redacción es el siguiente:

---
version: '2'
services:
  zookeeper:
    image: confluentinc/cp-zookeeper:6.1.0
    hostname: zookeeper
    container_name: zookeeper
    ports:
      - "2181:2181"
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000

  broker:
    image: confluentinc/cp-server:6.1.0
    hostname: broker
    container_name: broker
    depends_on:
      - zookeeper
    ports:
      - "9092:9092"
      - "9101:9101"
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181'
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://broker:29092,PLAINTEXT_HOST://localhost:9092
      KAFKA_METRIC_REPORTERS: io.confluent.metrics.reporter.ConfluentMetricsReporter
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
      KAFKA_CONFLUENT_LICENSE_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_CONFLUENT_BALANCER_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
      KAFKA_JMX_PORT: 9101
      KAFKA_JMX_HOSTNAME: localhost
      KAFKA_CONFLUENT_SCHEMA_REGISTRY_URL: http://schema-registry:8081
      CONFLUENT_METRICS_REPORTER_BOOTSTRAP_SERVERS: broker:29092
      CONFLUENT_METRICS_REPORTER_TOPIC_REPLICAS: 1
      CONFLUENT_METRICS_ENABLE: 'true'
      CONFLUENT_SUPPORT_CUSTOMER_ID: 'anonymous'

  schema-registry:
    image: confluentinc/cp-schema-registry:6.1.0
    hostname: schema-registry
    container_name: schema-registry
    depends_on:
      - broker
    ports:
      - "8081:8081"
    environment:
      SCHEMA_REGISTRY_HOST_NAME: schema-registry
      SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: 'broker:29092'
      SCHEMA_REGISTRY_LISTENERS: http://0.0.0.0:8081

  connect:
    image: cnfldemos/cp-server-connect-datagen:0.4.0-6.1.0
    hostname: connect
    container_name: connect
    depends_on:
      - broker
      - schema-registry
    ports:
      - "8083:8083"
    environment:
      CONNECT_BOOTSTRAP_SERVERS: 'broker:29092'
      CONNECT_REST_ADVERTISED_HOST_NAME: connect
      CONNECT_REST_PORT: 8083
      CONNECT_GROUP_ID: compose-connect-group
      CONNECT_CONFIG_STORAGE_TOPIC: docker-connect-configs
      CONNECT_CONFIG_STORAGE_REPLICATION_FACTOR: 1
      CONNECT_OFFSET_FLUSH_INTERVAL_MS: 10000
      CONNECT_OFFSET_STORAGE_TOPIC: docker-connect-offsets
      CONNECT_OFFSET_STORAGE_REPLICATION_FACTOR: 1
      CONNECT_STATUS_STORAGE_TOPIC: docker-connect-status
      CONNECT_STATUS_STORAGE_REPLICATION_FACTOR: 1
      CONNECT_KEY_CONVERTER: org.apache.kafka.connect.storage.StringConverter
      CONNECT_VALUE_CONVERTER: io.confluent.connect.avro.AvroConverter
      CONNECT_VALUE_CONVERTER_SCHEMA_REGISTRY_URL: http://schema-registry:8081
      # CLASSPATH required due to CC-2422
      CLASSPATH: /usr/share/java/monitoring-interceptors/monitoring-interceptors-6.1.0.jar
      CONNECT_PRODUCER_INTERCEPTOR_CLASSES: "io.confluent.monitoring.clients.interceptor.MonitoringProducerInterceptor"
      CONNECT_CONSUMER_INTERCEPTOR_CLASSES: "io.confluent.monitoring.clients.interceptor.MonitoringConsumerInterceptor"
      CONNECT_PLUGIN_PATH: "/usr/share/java,/usr/share/confluent-hub-components"
      CONNECT_LOG4J_LOGGERS: org.apache.zookeeper=ERROR,org.I0Itec.zkclient=ERROR,org.reflections=ERROR

  control-center:
    image: confluentinc/cp-enterprise-control-center:6.1.0
    hostname: control-center
    container_name: control-center
    depends_on:
      - broker
      - schema-registry
      - connect
      - ksqldb-server
    ports:
      - "9021:9021"
    environment:
      CONTROL_CENTER_BOOTSTRAP_SERVERS: 'broker:29092'
      CONTROL_CENTER_CONNECT_CLUSTER: 'connect:8083'
      CONTROL_CENTER_KSQL_KSQLDB1_URL: "http://ksqldb-server:8088"
      CONTROL_CENTER_KSQL_KSQLDB1_ADVERTISED_URL: "http://localhost:8088"
      CONTROL_CENTER_SCHEMA_REGISTRY_URL: "http://schema-registry:8081"
      CONTROL_CENTER_REPLICATION_FACTOR: 1
      CONTROL_CENTER_INTERNAL_TOPICS_PARTITIONS: 1
      CONTROL_CENTER_MONITORING_INTERCEPTOR_TOPIC_PARTITIONS: 1
      CONFLUENT_METRICS_TOPIC_REPLICATION: 1
      PORT: 9021

  ksqldb-server:
    image: confluentinc/cp-ksqldb-server:6.1.0
    hostname: ksqldb-server
    container_name: ksqldb-server
    depends_on:
      - broker
      - connect
    ports:
      - "8088:8088"
    environment:
      KSQL_CONFIG_DIR: "/etc/ksql"
      KSQL_BOOTSTRAP_SERVERS: "broker:29092"
      KSQL_HOST_NAME: ksqldb-server
      KSQL_LISTENERS: "http://0.0.0.0:8088"
      KSQL_CACHE_MAX_BYTES_BUFFERING: 0
      KSQL_KSQL_SCHEMA_REGISTRY_URL: "http://schema-registry:8081"
      KSQL_PRODUCER_INTERCEPTOR_CLASSES: "io.confluent.monitoring.clients.interceptor.MonitoringProducerInterceptor"
      KSQL_CONSUMER_INTERCEPTOR_CLASSES: "io.confluent.monitoring.clients.interceptor.MonitoringConsumerInterceptor"
      KSQL_KSQL_CONNECT_URL: "http://connect:8083"
      KSQL_KSQL_LOGGING_PROCESSING_TOPIC_REPLICATION_FACTOR: 1
      KSQL_KSQL_LOGGING_PROCESSING_TOPIC_AUTO_CREATE: 'true'
      KSQL_KSQL_LOGGING_PROCESSING_STREAM_AUTO_CREATE: 'true'

  ksqldb-cli:
    image: confluentinc/cp-ksqldb-cli:6.1.0
    container_name: ksqldb-cli
    depends_on:
      - broker
      - connect
      - ksqldb-server
    entrypoint: /bin/sh
    tty: true

  ksql-datagen:
    image: confluentinc/ksqldb-examples:6.1.0
    hostname: ksql-datagen
    container_name: ksql-datagen
    depends_on:
      - ksqldb-server
      - broker
      - schema-registry
      - connect
    command: "bash -c 'echo Waiting for Kafka to be ready... && \
                       cub kafka-ready -b broker:29092 1 40 && \
                       echo Waiting for Confluent Schema Registry to be ready... && \
                       cub sr-ready schema-registry 8081 40 && \
                       echo Waiting a few seconds for topic creation to finish... && \
                       sleep 11 && \
                       tail -f /dev/null'"
    environment:
      KSQL_CONFIG_DIR: "/etc/ksql"
      STREAMS_BOOTSTRAP_SERVERS: broker:29092
      STREAMS_SCHEMA_REGISTRY_HOST: schema-registry
      STREAMS_SCHEMA_REGISTRY_PORT: 8081

  rest-proxy:
    image: confluentinc/cp-kafka-rest:6.1.0
    depends_on:
      - broker
      - schema-registry
    ports:
      - 8082:8082
    hostname: rest-proxy
    container_name: rest-proxy
    environment:
      KAFKA_REST_HOST_NAME: rest-proxy
      KAFKA_REST_BOOTSTRAP_SERVERS: 'broker:29092'
      KAFKA_REST_LISTENERS: "http://0.0.0.0:8082"
      KAFKA_REST_SCHEMA_REGISTRY_URL: 'http://schema-registry:8081'

Ejecute nohup docker compose up > ./kafka.log 2>&1 & para comenzar. Ingrese localhost:9021 en el navegador para ver información relacionada con Kafka en la interfaz de la consola.

Ejecute la aplicación 1 y la aplicación 2 respectivamente, y luego llame a la interfaz POST http://localhost:8080/remotecommand para enviar solicitudes comerciales, como los siguientes comandos:

curl --location --request POST 'http://localhost:8080/sendcommand' \
--header 'Content-Type: application/json' \
--data-raw '{
    "vin": "ABC123",
    "command": "engine-start"
}'

En la consola de Kafka, puede ver que hay un tema de mensaje de comando remoto y se envía y consume un mensaje.

Inicio Prometeo y Grafana

También use docker compose para comenzar, el contenido del archivo de redacción es el siguiente:

services:
  prometheus:
    image: prom/prometheus-linux-amd64
    #network_mode: host
    container_name: prometheus
    restart: unless-stopped
    volumes:
      - ./config:/etc/prometheus/
    command:
      - '--config.file=/etc/prometheus/prometheus.yaml'
    ports:
      - 9090:9090
  grafana:
    image: grafana/grafana
    user: '472'
    #network_mode: host
    container_name: grafana
    restart: unless-stopped
    links:
      - prometheus:prometheus
    volumes:
      - ./data/grafana:/var/lib/grafana
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    ports:
      - 3000:3000
    depends_on:
      - prometheus

Cree un nuevo directorio de configuración en el directorio de archivos de redacción, que almacena el archivo de configuración de Prometheus, el contenido es el siguiente:

scrape_configs:
  - job_name: 'Spring Boot Application input'
    metrics_path: '/actuator/prometheus'
    scrape_interval: 2s
    static_configs:
      - targets: ['172.17.0.1:7777']
        labels:
          application: 'My Spring Boot Application'

La configuración de objetivos aquí es la dirección expuesta por la aplicación 2, y metrics_path es la ruta para recopilar métricas.

Cree un nuevo directorio de datos/grafana en el directorio de archivos de redacción y móntelo en el directorio de archivos de Grafana. Tenga en cuenta que debe usar chmod 777 para modificar los permisos del directorio, de lo contrario, Grafana informará un error de permiso.

Ejecute nohup docker compose up > ./prometheus.log 2>&1 y ejecute.

Abra localhost: 9090 para acceder a la página de Prometheus, y luego podemos ingresar kafka para buscar, y podemos ver los datos del índice kafka_process_latency informados por la aplicación 2, y se llevan a cabo las estadísticas de los tres intervalos percentiles de 0.15, 0.5 y 0.95 según nuestra definición.

Abra localhost:3000 para acceder a la página de Grafana, configure la fuente de datos, seleccione la dirección del contenedor de Prometheus y luego guarde y pruebe. Luego, puede crear un nuevo tablero y luego mostrar el gráfico indicador de kafka_process_latency en el informe.

[Continuará] Es necesario agregar la métrica Counter para llamar a la interfaz Http y definir más informes en Grafana, incluidos otros indicadores de servicio, etc.

Supongo que te gusta

Origin blog.csdn.net/gzroy/article/details/127769341
Recomendado
Clasificación