美文网首页
Springboot+prometheus监控数据库连接池

Springboot+prometheus监控数据库连接池

作者: 西5d | 来源:发表于2020-12-16 11:09 被阅读0次

    背景

    之前有提到过使用Prometheus做Springboot的监控,这次以一个实例来说明,通过一种统一的方式,监控数据库连接池的运行情况。

    原理

    其实在Springboot内部监控都是结合了micrometer来做的,基于他的MeterRegistry,实现多种方式的监控,如PrometheusMeterRegistry。他也提供了对于很多的监控实现,如缓存,线程池,tomcat,JVM,OKhttp等。对应在io.micrometer.core.instrument.binder包中。

    数据库相关datasource的监控默认在spring-boot-actuator(使用版本为2.1.4.RELEASE),具体在org.springframework.boot.actuate.metrics.jdbc.DataSourcePoolMetrics。这里会参考已有数据库连接池监控的实现,完成对项目中使用的druid的简单监控。

    关于mecrometer的相关的依赖,可以引入如下:

         <dependency>
              <groupId>io.micrometer</groupId>
              <artifactId>micrometer-core</artifactId>
              <version>1.5.1</version>
          </dependency>
          <dependency>
              <groupId>io.micrometer</groupId>
              <artifactId>micrometer-registry-prometheus</artifactId>
              <version>1.5.1</version>
          </dependency>
    
    

    步骤

    分析已有实现

    我们看org.springframework.boot.actuate.metrics.jdbc.DataSourcePoolMetrics中的实现,是使用的org.springframework.boot.jdbc.metadata.DataSourcePoolMetadata来获取数据库连接池运行数据的。再看这个接口,在spring-boot:2.1.4.RELEASE中包括tomcat,dbcp2,hikari是有默认实现,比如下面是hikari的实现,获取到了当前运行连接数,最高连接数,探活SQL等。

    /**
     * {@link DataSourcePoolMetadata} for a Hikari {@link DataSource}.
     *
     * @author Stephane Nicoll
     * @since 2.0.0
     */
    public class HikariDataSourcePoolMetadata
            extends AbstractDataSourcePoolMetadata<HikariDataSource> {
    
        public HikariDataSourcePoolMetadata(HikariDataSource dataSource) {
            super(dataSource);
        }
    
        @Override
        public Integer getActive() {
            try {
                return getHikariPool().getActiveConnections();
            }
            catch (Exception ex) {
                return null;
            }
        }
    
        private HikariPool getHikariPool() {
            return (HikariPool) new DirectFieldAccessor(getDataSource())
                    .getPropertyValue("pool");
        }
    
        @Override
        public Integer getMax() {
            return getDataSource().getMaximumPoolSize();
        }
    
        @Override
        public Integer getMin() {
            return getDataSource().getMinimumIdle();
        }
    
        @Override
        public String getValidationQuery() {
            return getDataSource().getConnectionTestQuery();
        }
    
        @Override
        public Boolean getDefaultAutoCommit() {
            return getDataSource().isAutoCommit();
        }
    
    }
    

    DataSourcePoolMetadata的定义,可以参考注释理解各个方法的意义。

    public interface DataSourcePoolMetadata {
    
        /**
         * Return the usage of the pool as value between 0 and 1 (or -1 if the pool is not
         * limited).
         * <ul>
         * <li>1 means that the maximum number of connections have been allocated</li>
         * <li>0 means that no connection is currently active</li>
         * <li>-1 means there is not limit to the number of connections that can be allocated
         * </li>
         * </ul>
         * This may also return {@code null} if the data source does not provide the necessary
         * information to compute the poll usage.
         * @return the usage value or {@code null}
         */
        Float getUsage();
    
        /**
         * Return the current number of active connections that have been allocated from the
         * data source or {@code null} if that information is not available.
         * @return the number of active connections or {@code null}
         */
        Integer getActive();
    
        /**
         * Return the maximum number of active connections that can be allocated at the same
         * time or {@code -1} if there is no limit. Can also return {@code null} if that
         * information is not available.
         * @return the maximum number of active connections or {@code null}
         */
        Integer getMax();
    
        /**
         * Return the minimum number of idle connections in the pool or {@code null} if that
         * information is not available.
         * @return the minimum number of active connections or {@code null}
         */
        Integer getMin();
    
        /**
         * Return the query to use to validate that a connection is valid or {@code null} if
         * that information is not available.
         * @return the validation query or {@code null}
         */
        String getValidationQuery();
    
        /**
         * The default auto-commit state of connections created by this pool. If not set
         * ({@code null}), default is JDBC driver default (If set to null then the
         * java.sql.Connection.setAutoCommit(boolean) method will not be called.)
         * @return the default auto-commit state or {@code null}
         */
        Boolean getDefaultAutoCommit();
    
    }
    

    具体看下org.springframework.boot.actuate.metrics.jdbc.DataSourcePoolMetrics的实现,这里将单个datasource导入,然后根据实现的DataSourcePoolMetadata,获取到了具体的运行数据,然后,本身实现了MeterBinder接口,所以最终通过在bindTo(MetricRegistry registry)调用bindPoolMetadata() -> bindDataSource()完成监控的记录。

    public class DataSourcePoolMetrics implements MeterBinder {
    
        private final DataSource dataSource;
    
        private final CachingDataSourcePoolMetadataProvider metadataProvider;
    
        private final Iterable<Tag> tags;
    
        public DataSourcePoolMetrics(DataSource dataSource,
                Collection<DataSourcePoolMetadataProvider> metadataProviders,
                String dataSourceName, Iterable<Tag> tags) {
            this(dataSource, new CompositeDataSourcePoolMetadataProvider(metadataProviders),
                    dataSourceName, tags);
        }
    
        public DataSourcePoolMetrics(DataSource dataSource,
                DataSourcePoolMetadataProvider metadataProvider, String name,
                Iterable<Tag> tags) {
            Assert.notNull(dataSource, "DataSource must not be null");
            Assert.notNull(metadataProvider, "MetadataProvider must not be null");
            this.dataSource = dataSource;
            this.metadataProvider = new CachingDataSourcePoolMetadataProvider(
                    metadataProvider);
            this.tags = Tags.concat(tags, "name", name);
        }
    
        @Override
        public void bindTo(MeterRegistry registry) {
            if (this.metadataProvider.getDataSourcePoolMetadata(this.dataSource) != null) {
                bindPoolMetadata(registry, "active", DataSourcePoolMetadata::getActive);
                bindPoolMetadata(registry, "max", DataSourcePoolMetadata::getMax);
                bindPoolMetadata(registry, "min", DataSourcePoolMetadata::getMin);
            }
        }
    
        private <N extends Number> void bindPoolMetadata(MeterRegistry registry,
                String metricName, Function<DataSourcePoolMetadata, N> function) {
            bindDataSource(registry, metricName,
                    this.metadataProvider.getValueFunction(function));
        }
    
        private <N extends Number> void bindDataSource(MeterRegistry registry,
                String metricName, Function<DataSource, N> function) {
            if (function.apply(this.dataSource) != null) {
                registry.gauge("jdbc.connections." + metricName, this.tags, this.dataSource,
                        (m) -> function.apply(m).doubleValue());
            }
        }
    
        private static class CachingDataSourcePoolMetadataProvider
                implements DataSourcePoolMetadataProvider {
    
            private static final Map<DataSource, DataSourcePoolMetadata> cache = new ConcurrentReferenceHashMap<>();
    
            private final DataSourcePoolMetadataProvider metadataProvider;
    
            CachingDataSourcePoolMetadataProvider(
                    DataSourcePoolMetadataProvider metadataProvider) {
                this.metadataProvider = metadataProvider;
            }
    
            public <N extends Number> Function<DataSource, N> getValueFunction(
                    Function<DataSourcePoolMetadata, N> function) {
                return (dataSource) -> function.apply(getDataSourcePoolMetadata(dataSource));
            }
    
            @Override
            public DataSourcePoolMetadata getDataSourcePoolMetadata(DataSource dataSource) {
                DataSourcePoolMetadata metadata = cache.get(dataSource);
                if (metadata == null) {
                    metadata = this.metadataProvider.getDataSourcePoolMetadata(dataSource);
                    cache.put(dataSource, metadata);
                }
                return metadata;
            }
    
        }
    
    }
    
    

    而关于什么时候使用了DataSourceMetric,可以看它的构造方法引用,找到org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration,如下,可以看到,是通过spring的依赖注入,完成对所有datasource构造DataSourceMetric并注册到MeterRegistry的。

            //注入所有Datasource ,结构为Map,名称和对应实例
            @Autowired
            public void bindDataSourcesToRegistry(Map<String, DataSource> dataSources) {
                dataSources.forEach(this::bindDataSourceToRegistry);
            }
    
            private void bindDataSourceToRegistry(String beanName, DataSource dataSource) {
                String dataSourceName = getDataSourceName(beanName);
                new DataSourcePoolMetrics(dataSource, this.metadataProviders, dataSourceName,
                        Collections.emptyList()).bindTo(this.registry);
            }
    
            /**
             * Get the name of a DataSource based on its {@code beanName}.
             * @param beanName the name of the data source bean
             * @return a name for the given data source
             */
            private String getDataSourceName(String beanName) {
                if (beanName.length() > DATASOURCE_SUFFIX.length()
                        && StringUtils.endsWithIgnoreCase(beanName, DATASOURCE_SUFFIX)) {
                    return beanName.substring(0,
                            beanName.length() - DATASOURCE_SUFFIX.length());
                }
                return beanName;
            }
    

    实现druid连接池监控

    完成了原理的分析,现在我们再来了解怎么实现对druid的监控。参考上面,定义一个自定义的DruidDataSourcePoolMetadata实现自DataSourcePoolMetadata或者AbstractDataSourcePoolMetadata。但目前问题是,上面使用的是
    CachingDataSourcePoolMetadataProvider一个内部类的provider,没法直接使用将DruidDataSourcePoolMetadata添加到里边。但回到DataSourcePoolMetricsDataSourcePoolMetricsAutoConfiguration,可以看到DataSourcePoolMetadataProvider可以注入多个。因此,再实现个针对DruidDataSourcePoolMetadataDataSourcePoolMetadataProvider,然后执行注入,就可以得到我们想要的功能。

        @Bean
        public DataSourcePoolMetadataProvider druidPoolDataSourceMetadataProvider() {
            return (dataSource) -> {
                DruidDataSource ds = DataSourceUnwrapper.unwrap(dataSource,
                        DruidDataSource.class);
                if (ds != null) {
                    return new DruidDataSourcePoolMetadata(ds);
                }
                return null;
            };
        }
        /**
             * 参考 org.springframework.boot.jdbc.metadata.HikariDataSourcePoolMetadata
             */
            private class DruidDataSourcePoolMetadata extends AbstractDataSourcePoolMetadata<DruidDataSource> {
                /**
                 * Create an instance with the data source to use.
                 *
                 * @param dataSource the data source
                 */
                DruidDataSourcePoolMetadata(DruidDataSource dataSource) {
                    super(dataSource);
                }
        
                @Override
                public Integer getActive() {
                    return getDataSource().getActiveCount();
                }
        
                @Override
                public Integer getMax() {
                    return getDataSource().getMaxActive();
                }
        
                @Override
                public Integer getMin() {
                    return getDataSource().getMinIdle();
                }
        
                @Override
                public String getValidationQuery() {
                    return getDataSource().getValidationQuery();
                }
        
                @Override
                public Boolean getDefaultAutoCommit() {
                    return getDataSource().isDefaultAutoCommit();
                }
            }
    

    结果

    结果

    总结

    以上就是本期的所有内容,感谢阅读。可以看到spring-boot在模块化方面特别好,各种自定义实现可以在多个层次很好的组合起来,对于开发人员来说是十分方便的。这个例子中很多的思路可以作为以后开发的参考。同时,druid本身自带很多运行参数的监控,参考com.alibaba.druid.stat,如果能添加更多指标的数据并和DataSourceMetric结合,也是不错的选择,看起来实现也并不是很复杂。

    参考资料

    1. micrometer
    2. custom_metrics_micrometer_prometheus_spring_boot_actuator

    相关文章

      网友评论

          本文标题:Springboot+prometheus监控数据库连接池

          本文链接:https://www.haomeiwen.com/subject/unsngktx.html