摘要
Druid数据源指标监控分数据采集和指标告警,是指通过开发java代码获取应用的Druid数据源指标并暴露到应用的指标端点,然后通过普米等监控工具拉取该指标,然后通过alert-manager、n9e等告警工具配置域值从而实现告警和监控的能力。
本文亮点
1、实现了一般的Druid数据源监控,
2、还额外支持tomcat内置JNDI做为底层数据源的Druid数据源监控
3、如需实现HikariCP数据源指标采集只需改一下代码中的指标名即可(代码可复用)
标签
JNDI数据源指标监控、Druid指标监控、HikariCP指标监控
一、效果图
二、数据流图/组件架构图
三、代码
1、Druid数据源指标配置类
package person.daizhongde.common.monitor.druid;
import person.daizhongde.datasources.DynamicDataSource;// AbstractRoutingDataSource 的实现类,用于支持Druid代理tomcat JNDI数据源
import com.alibaba.druid.pool.DruidDataSource;
import io.prometheus.client.CollectorRegistry;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.jdbc.DataSourceUnwrapper;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @Description: Druid数据源指标配置类
* @Author: bricklayer(飞火流星02027)
* @CreateDate: 2024/10/23 10:28 AM
* @Version: 1.0
*/
@Slf4j
@Configuration
@ConditionalOnClass({DruidDataSource.class, CollectorRegistry.class})
@ConditionalOnProperty(name = "monitor.datasource.druid.enabled", havingValue = "true", matchIfMissing = false )
public class DruidMetricsConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(DruidMetricsConfiguration.class);
private final CollectorRegistry registry;
public DruidMetricsConfiguration(CollectorRegistry registry) {
this.registry = registry;
}
@Autowired
public void bindMetricsRegistryToDruidDataSources(Collection<DataSource> dataSources) {
Map<String, DruidDataSource> druidDataSources = new LinkedHashMap<>();
for (DataSource dataSource : dataSources) {
// 如果没有使用动态数据源代理可以注释掉if代码,只保留else中的
if(dataSource instanceof DynamicDataSource){
DynamicDataSource dynamicDataSource = (DynamicDataSource)dataSource;
Map<Object, Object> map = dynamicDataSource.getTargetDataSources();
//Lambda表达式
map.forEach((k,v) ->{
// key 为 name , v为 datasource
log.info("数据源:{}====={}",k, v);
// System.out.println("$$$$$$$$$$$$$$ test5 $$$$$$$$");
DataSource crmDataSource = (DataSource) map.get(k);
DruidDataSource druidDataSource = DataSourceUnwrapper.unwrap(crmDataSource, DruidDataSource.class);
if (druidDataSource != null) {
druidDataSources.put(druidDataSource.getName()+"-"+k, druidDataSource);
}else{
Object obj1 = map.get(k);
try {
druidDataSource = (DruidDataSource)DruidAopUtils.getProxyTarget(obj1 );
druidDataSources.put( druidDataSource.getName()+"-"+k, druidDataSource );
} catch (Exception e) {
log.error("DruidAopUtils获取Druid数据源代码时出错!error:{}",e.getLocalizedMessage());
throw new RuntimeException(e);
}
}
});
}else{
DruidDataSource druidDataSource = DataSourceUnwrapper.unwrap(dataSource, DruidDataSource.class);
if (druidDataSource != null) {
druidDataSources.put(druidDataSource.getName(), druidDataSource);
}
}
}
DruidCollector druidCollector = new DruidCollector( druidDataSources );
druidCollector.register(registry);
}
}
2、Druid数据源指标聚合类
package person.daizhongde.common.monitor.druid;
import com.alibaba.druid.pool.DruidDataSource;
import io.prometheus.client.Collector;
import io.prometheus.client.GaugeMetricFamily;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
/**
* @Description: Druid数据源指标聚合类
* @Author: bricklayer(飞火流星02027)
* @CreateDate: 2024/10/23 10:28 AM
* @Version: 1.0
*/
public class DruidCollector extends Collector {
private static final List<String> LABEL_NAMES = new ArrayList<String>();
static {
LABEL_NAMES.add("pool");
LABEL_NAMES.add("username");
LABEL_NAMES.add("url");
}
private final Map<String, DruidDataSource> dataSources;
public DruidCollector(Map<String, DruidDataSource> dataSources) {
this.dataSources = dataSources;
}
@Override
public List<MetricFamilySamples> collect() {
return Arrays.asList(
createGauge("druid_active_count", "Active count",
druidDataSource -> (double) druidDataSource.getActiveCount()),
createGauge("druid_active_peak", "Active peak",
druidDataSource -> (double) druidDataSource.getActivePeak()),
createGauge("druid_error_count", "Error count",
druidDataSource -> (double) druidDataSource.getErrorCount()),
createGauge("druid_execute_count", "Execute count",
druidDataSource -> (double) druidDataSource.getExecuteCount()),
createGauge("druid_max_active", "Max active",
druidDataSource -> (double) druidDataSource.getMaxActive()),
createGauge("druid_min_idle", "Min idle",
druidDataSource -> (double) druidDataSource.getMinIdle()),
createGauge("druid_max_wait", "Max wait",
druidDataSource -> (double) druidDataSource.getMaxWait()),
createGauge("druid_max_wait_thread_count", "Max wait thread count",
druidDataSource -> (double) druidDataSource.getMaxWaitThreadCount()),
createGauge("druid_pooling_count", "Pooling count",
druidDataSource -> (double) druidDataSource.getPoolingCount()),
createGauge("druid_pooling_peak", "Pooling peak",
druidDataSource -> (double) druidDataSource.getPoolingPeak()),
createGauge("druid_rollback_count", "Rollback count",
druidDataSource -> (double) druidDataSource.getRollbackCount()),
createGauge("druid_wait_thread_count", "Wait thread count",
druidDataSource -> (double) druidDataSource.getWaitThreadCount())
// 还可添加其他指标
// .....
);
}
private GaugeMetricFamily createGauge(String metric, String help,
Function<DruidDataSource, Double> metricValueFunction) {
GaugeMetricFamily metricFamily = new GaugeMetricFamily(metric, help, LABEL_NAMES);
dataSources.forEach((s, druidDataSource) -> {
List<String> list = new ArrayList<String>();
list.add(s);
list.add(druidDataSource.getUsername());
String url = druidDataSource.getUrl();
url = url.length()>60?url.substring(0,60)+"...":url;
list.add( url );
metricFamily.addMetric(
list,
metricValueFunction.apply(druidDataSource)
);
});
return metricFamily;
}
}
3、从代理对象中取druid数据源对象的工具类
package person.daizhongde.common.monitor.druid;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.AopProxy;
import org.springframework.aop.support.AopUtils;
import java.lang.reflect.Field;
/**
* @Description: 从代理对象中取druid数据源对象的工具类
* @Author: bricklayer(飞火流星02027)
* @CreateDate: 2024/10/23 10:28 AM
* @Version: 1.0
*/
public class DruidAopUtils {
public static Object getProxyTarget(Object proxy) throws Exception {
//判断是否是代理对象
if(AopUtils.isAopProxy(proxy)){
//cglib 代理
if(AopUtils.isCglibProxy(proxy)){
//通过暴力反射拿到代理对象的拦截器属性,从拦截器获取目标对象
Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
h.setAccessible(true);
Object dynamicAdvisedInterceptor = h.get(proxy);
Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport)advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();
//返回目标对象
return target;
}
//jdk代理
if(AopUtils.isJdkDynamicProxy(proxy)){
//通过暴力反射拿到代理对象的拦截器属性,从拦截器获取目标对象
Field h = proxy.getClass().getSuperclass().getDeclaredField("h");
h.setAccessible(true);
AopProxy aopProxy = (AopProxy) h.get(proxy);
Field advised = aopProxy.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport)advised.get(aopProxy)).getTargetSource().getTarget();
return target;
}
}
return null;
}
}
四、参考资料(不支持JNDI数据源)
某个github项目,不记得名字了,找到再补上
赠送
Druid官方文档
https://github.com/alibaba/druid/wiki
Druid配置文档
https://github.com/alibaba/druid/wiki/DruidDataSource配置属性列表