Məqalələr

Hikari Connection Pool Metrics

Written by Mushfiq Mammadov

Çoxdandır blogda məqalə yazmıram, karantində mən də şeytanın qıçını sındırım dedim 🙂

Bu məqalədə hikari-connection-pool`da mövcud olan connection`ların vəziyyəti haqqında real-time məlumatları izləmək üçün manual necə metrics yazmaq olar ona baxacağıq.

Çox güman ki, devops tərəfdə connection metrics ilə bağlı çox gözəl hazır tool`lar var, bu məqalədə sadəcə qısa və yığcam şəkildə necə metrics yazmaq olar onu görəcəyik. Bunu develop etmək niyyəti əslində hazırda üzərində işlədiyim proyekt ilə əlaqədar yarandı. Proyekt connection`lara həssas bir proyektdi, yüngül load test etmək istəyirdim və mənə connection`ların özlərini necə aparmağına tool`suz filan rahat şəkildə, istədiyim vaxt baxa bilmək lazım oldu. Və beləcə bu kod meydana gəldi.

Yəqin fikir vermisiz, loglarda Hikari`inin DEBUG modunu açıq etsəniz müəyyən intervaldan bir aşağıdakı logun çıxdığını görürsünüz:

HikariPool-1 - Pool stats (total=10, active=0, idle=10, waiting=0)

Mənə də bu məlumatlar kifayət edirdi. Qərara aldım ki, bir endpoint yazım, istədiyim vaxt çağırıb bu infolara baxım. Bu məqalədə də bu endpoint`i develop edəcəyik. Getdik 🙂

Deməli metrics ilə işləmək üçün Spring Boot Actuator kitabxanasından istifadə edəcəyik. Spring Boot 2 versiyasından sonra actuator`da 2 endpointdən (/health/info) başqa digər bütün endpoint`lər default olaraq disabled gəlir. Əgər bütün endpoint`ləri enabled etmək istəsək o zaman application.properties faylına aşağıdakı sətri əlavə etməliyik:

management.endpoints.web.exposure.include=*

Bizə metrics lazım olduğuna görə sadəcə onu əlavə edəcəyik. Metrics`də mövcud olan bütün parametrlərin siyahısına baxmaq üçün aşağıdakı endpoint`i çağırırıq:

http://localhost:8888/actuator/metrics

{
    "names": [
        "jvm.threads.states",
        "hikaricp.connections.pending",
        "jvm.gc.memory.promoted",
        "hikaricp.connections",
        "jvm.memory.max",
        "hikaricp.connections.active",
        "jvm.memory.used",
        "hikaricp.connections.idle",
        "hikaricp.connections.creation",
        "jvm.gc.max.data.size",
        "jvm.memory.committed",
        "system.cpu.count",
        "logback.events",
        "jvm.gc.pause",
        "jvm.buffer.memory.used",
        "tomcat.sessions.created",
        "jvm.threads.daemon",
        "hikaricp.connections.max",
        "hikaricp.connections.min",
        "system.cpu.usage",
        "jvm.gc.memory.allocated",
        "hikaricp.connections.usage",
        "tomcat.sessions.expired",
        "jvm.threads.live",
        "jvm.threads.peak",
        "hikaricp.connections.timeout",
        "process.uptime",
        "jdbc.connections.active",
        "tomcat.sessions.rejected",
        "hikaricp.connections.acquire",
        "process.cpu.usage",
        "jvm.classes.loaded",
        "jdbc.connections.max",
        "jdbc.connections.min",
        "jvm.classes.unloaded",
        "tomcat.sessions.active.current",
        "tomcat.sessions.alive.max",
        "jvm.gc.live.data.size",
        "http.server.requests",
        "jvm.buffer.count",
        "jdbc.connections.idle",
        "jvm.buffer.total.capacity",
        "tomcat.sessions.active.max",
        "process.start.time",
    ]
}

Bu siyahıdakı hansı parametrlə bağlı məlumata baxmaq istəyiriksə, həmin parametrin adını yuxarıda qeyd etdiyimiz endpointdən sonra / qoyaraq əlavə edib çağırırıq. Məsələn HikariCP`də mövcud olan bütün connection`ların sayına baxmaq üçün aşağıdakı endpoint`i çağırırıq:

http://localhost:8888/actuator/metrics/hikaricp.connections

{
   "name":"hikaricp.connections",
   "description":"Total connections",
   "baseUnit":null,
   "measurements":[
      {
         "statistic":"VALUE",
         "value":35
      }
   ],
   "availableTags":[
      {
         "tag":"pool",
         "values":[
            "mysql-pool",
            "oracle-pool",
            "h2-pool"
         ]
      }
   ]
}

Qayıdan response`dan görürük ki, bizim 3 connection pool`umuz və ümumilikdə 35 mövcud connection`umuz var. Amma hansı connection pool`a nə qədər connection aiddir onu ayrıca görə bilmirik.

Yazdıqlarımızı yekunlaşdırsaq deməli hazırkı vəziyyətdə bizim 2 çətinliyimiz var:

  1. Hansı parametrlə bağlı məlumatı görmək istəyiriksə, onun üçün ayrıca endpoint çağırmalıyıq; əgər 4 parametr üzrə məlumat lazımdısa, 4 fərqli endpoint çağırmalıyıq;
  2. Tutaq ki, bir neçə fərqli endpoint çağırmağa razı olduq, amma bizim əldə etdiyimiz məlumat yenə də ümumi məlumat olur. Əgər bizim bir neçə connection-pool`umuz varsa, hər pool üzrə məlumatları ayrıca görə bilməyəcəyik, necə ki, yuxarıdakı response`da göstərilən 35 connection 3 pool`a aiddir.

Buna görə də məlumatları istədiyimiz formada əldə etmək üçün manual metrics yazacağıq və bunun üçün actuator kitabxanasının MetricsEndpoint klasını istifadə edəcəyik. Proyektimizin strukturu aşağıdakı kimi olacaq:

hikari-connection-pool-metrics

MetricsController.java

@RestController
@RequestMapping("/metrics")
public class MetricsController {

    private final MetricsService metricsService;

    public MetricsController(MetricsService metricsService) {
        this.metricsService = metricsService;
    }

    @GetMapping("/hikari/connections")
    public Map<String, Map> getHikariConnectionsInfo() {
        return metricsService.hikariConnectionPoolMetrics();
    }
}

 

MetricsService.java

@Service
public class MetricsService {

    private final MetricsEndpoint metricsEndpoint;
    private final Map<String, String> hikariFields;

    public MetricsService(MetricsEndpoint metricsEndpoint) {
        this.metricsEndpoint = metricsEndpoint;
        this.hikariFields = MetricsUtil.hikariMetricFields();
    }

    public Map<String, Map> hikariConnectionPoolMetrics() {
        MetricResponse hikariPoolInfo = metricsEndpoint.metric(hikariFields.get("total"), null);
        AvailableTag availableTag = hikariPoolInfo.getAvailableTags().get(0);
        String tagName = availableTag.getTag();
        Set<String> tagValues = availableTag.getValues();
        Map<String, Map> connections = new HashMap<>();

        for (String tagValue : tagValues) {
            Map<String, Object> params = new LinkedHashMap<>();
            hikariFields.forEach((k, v) -> {
                MetricResponse metricResponse = metricsEndpoint.metric(v, Arrays.asList(tagName + ":" + tagValue));
                Integer val = metricResponse.getMeasurements().get(0).getValue().intValue();
                params.put(k, val);
            });
            connections.put(tagValue, params);
        }
        return connections;
    }
}

 

MetricsUtil.java

public class MetricsUtil {

    private MetricsUtil() {
    }

    public static Map<String, String> hikariMetricFields() {
        Map<String, String> hikariFields = new LinkedHashMap<>();
        hikariFields.put("total", "hikaricp.connections");
        hikariFields.put("active", "hikaricp.connections.active");
        hikariFields.put("idle", "hikaricp.connections.idle");
        hikariFields.put("waiting", "hikaricp.connections.pending");
        return hikariFields;
    }
}

 

DatabaseConfig.java

@Configuration
public class DatabaseConfig {

    @Primary
    @Bean("oracleDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.oracle-db")
    public DataSource oracleDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean("mySqlDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.mysql-db")
    public DataSource mySqlDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean("h2DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.h2-db")
    public DataSource h2DataSource() {
        return DataSourceBuilder.create().build();
    }
}

 

application.yml

server:
  port: 8888

spring:
  application:
    name: hikari-connection-pool-metrics
  datasource:
    oracle-db:
      driver-class-name: oracle.jdbc.OracleDriver
      jdbc-url: jdbc:oracle:thin:@localhost:1521:xe
      username: test
      password: test
      pool-name: oracle-pool
      maximum-pool-size: 20
      data-source-properties:
        oracle.jdbc.implicitStatementCacheSize: 100
    mysql-db:
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://localhost:3306/test
      username: root
      password: root
      pool-name: mysql-pool
      data-source-properties:
        cachePrepStmts: true
        prepStmtCacheSize: 250
        prepStmtCacheSqlLimit: 2048
    h2-db:
      driver-class-name: org.h2.Driver
      jdbc-url: jdbc:h2:mem:testdb
      username: sa
      password:
      pool-name: h2-pool
      maximum-pool-size: 5

management:
  endpoints:
    web:
      exposure:
        include: info, health, metrics

logging:
  level:
    com.zaxxer.hikari: DEBUG

 

İndi aşağıdakı endpoint`i çağırmaqla nəticəni artıq öz istədiyimiz şəkildə görə bilərik:

http://localhost:8888/metrics/hikari/connections

{
   "oracle-pool":{
      "total":20,
      "active":0,
      "idle":20,
      "waiting":0
   },
   "h2-pool":{
      "total":5,
      "active":0,
      "idle":5,
      "waiting":0
   },
   "mysql-pool":{
      "total":10,
      "active":0,
      "idle":10,
      "waiting":0
   }
}

Kodları yəqin ki, izah etməyə ehtiyac yoxdur, MetricsEndpoint klasının metric() metodunu araşdırmaq kifayət edir. Parametrlərin ümumi siyahısına baxmaq üçün MetricsEndpoint klasının listNames() metodundan istifadə edə bilərsiz. Mənə hikari ilə bağlı 4 parametr lazım olduğundan sadəcə onları qruplaşdırmışam. Amma siz istəsəniz bunu özünüzə lazım olan digər şəkillərdə təkmilləşdirə bilərsiz.

Kodlara tam şəkildə Githubda aşağıdakı repo üzərindən baxa bilərsiz:

https://github.com/mmushfiq/hikari-connection-pool-metrics.git

Bu məqaləmiz də bu qədər, ümid edirəm faydalı olmuşdur. Növbəti məqalələrdə görüşənədək. Necə deyərlər evdə qalın, sağlam qalın, amma arada imkan olduqca yazın pozun))

About the author

Mushfiq Mammadov

2 Comments

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.

 

This site uses Akismet to reduce spam. Learn how your comment data is processed.