druid 源码解析 (7)

昨天我们了解了一些druid关于mBean的内容,今天我们在昨天的这个demo上再试试druid引以为傲的另一方面-druid监控

首先我们打开昨天的应用,在pom中加入以下的依赖:

<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>5.1.47</version>
</dependency>
<dependency>

   <groupId>com.alibaba</groupId>
   <artifactId>druid</artifactId>
   <version>1.1.10</version>
</dependency>

<!--druid-->
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>druid-spring-boot-starter</artifactId>
   <version>1.1.10</version>
</dependency>
复制代码

本文的demo都是基于spring boot 和yml格式实现的

然后我们打开yml文件把昨天用的hikari改成druid

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
    druid:
      #初始化大小
      initialSize: 5
      #最小值
      minIdle: 5
      #最大值
      maxActive: 20
      #最大等待时间,配置获取连接等待超时,时间单位都是毫秒ms
      maxWait: 60000
      #配置间隔多久才进行一次检测,检测需要关闭的空闲连接
      timeBetweenEvictionRunsMillis: 60000
      #配置一个连接在池中最小生存的时间
      minEvictableIdleTimeMillis: 300000
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      poolPreparedStatements: true
      # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,
      #'wall'用于防火墙,SpringBoot中没有log4j,我改成了log4j2
      filters: stat,wall,log4j2
      #最大PSCache连接
      maxPoolPreparedStatementPerConnectionSize: 20
      useGlobalDataSourceStat: true
      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
      # 配置StatFilter
      web-stat-filter:
        #默认为false,设置为true启动
        enabled: true
        url-pattern: "/*"
        exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
      #配置StatViewServlet
      stat-view-servlet:
        url-pattern: "/druid/*"
        #允许那些ip
        allow: 127.0.0.1
        #禁止那些ip
        deny: 192.168.1.102
        #是否可以重置
        reset-enable: true
        #启用
        enabled: true
复制代码

再然后我们需要引入一个配置文件,有关于monitor的相关配置

@Configuration
public class DruidConfig {

    // 配置文件spring.datasource绑定
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource initDruid() {
        return new DruidDataSource();
    }

    @Bean
    public ServletRegistrationBean druidStatViewServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        servletRegistrationBean.addInitParameter("allow", "");
        servletRegistrationBean.addInitParameter("loginUsername", "root");
        servletRegistrationBean.addInitParameter("loginPassword", "root");
        return servletRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean druidStatFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }
}
复制代码

然后运行项目就自动启动了,只要在一个浏览器中输入网址http://localhost:8080/druid/login.html 就可以看到druidmonitor的网址,然后使用我们刚刚在config里面配置的用户名密码就可以登陆了

屏幕快照 2021-11-17 下午9.55.09.png

随便调用了一下昨天写的这个mBean的demo中的接口,就可以看到:

屏幕快照 2021-11-17 下午9.57.06.png 我们刚刚调用的接口信息全部出现在了monitor的网页上。

说到监控其实首先要找到监控的具体配置项:

# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,
#'wall'用于防火墙,SpringBoot中没有log4j,我改成了log4j2
filters: stat,wall,log4j2
#最大PSCache连接
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

web-stat-filter:
  #默认为false,设置为true启动
  enabled: true
  url-pattern: "/*"
  exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
复制代码

filters=stat,wall,log4j2这些配置的具体含义是什么呢? 我们可以在druid找到具体的实现代码部分

public class StatFilter extends FilterEventAdapter implements StatFilterMBean 
复制代码

StatFilter 里面实现了一些对于监控的配置:


public boolean isMergeSql() {
    return mergeSql;
}

public void setMergeSql(boolean mergeSql) {
    this.mergeSql = mergeSql;
}

public String getSlowSqlLogLevel() {
    return slowSqlLogLevel;
}

public void setSlowSqlLogLevel(String slowSqlLogLevel) {
    this.slowSqlLogLevel = slowSqlLogLevel;
}

@Deprecated
public String mergeSql(String sql) {
    return this.mergeSql(sql, dbType);
}

public String mergeSql(String sql, String dbType) {
    return mergeSql(sql, DbType.of(dbType));
}

public String mergeSql(String sql, DbType dbType) {
    if (!mergeSql) {
        return sql;
    }

    try {
        sql = ParameterizedOutputVisitorUtils.parameterize(sql, dbType, null, null, null);
    } catch (Exception e) {
        LOG.error("merge sql error, dbType " + dbType + ", druid-" + VERSION.getVersionNumber() + ", sql : " + sql, e);
    }

    return sql;
}
复制代码

比如这个合并sql 可以把这样的sql:

select * from t where id = 1
select * from t where id = 2
select * from t where id = 3
复制代码

合并为一个

select * from t where id = ?
复制代码

这样就大大的减少了监控的sql量。

初始化的类中除了合并sql还有很多关于慢sql的配置,

public void configFromProperties(Properties properties) {
    if (properties == null) {
        return;
    }

    {
        String property = properties.getProperty(SYS_PROP_MERGE_SQL);
        if ("true".equals(property)) {
            this.mergeSql = true;
        } else if ("false".equals(property)) {
            this.mergeSql = false;
        }
    }

    {
        String property = properties.getProperty(SYS_PROP_SLOW_SQL_MILLIS);
        if (property != null && property.trim().length() > 0) {
            property = property.trim();
            try {
                this.slowSqlMillis = Long.parseLong(property);
            } catch (Exception e) {
                LOG.error("property 'druid.stat.slowSqlMillis' format error");
            }
        }
    }

    {
        String property = properties.getProperty(SYS_PROP_LOG_SLOW_SQL);
        if ("true".equals(property)) {
            this.logSlowSql = true;
        } else if ("false".equals(property)) {
            this.logSlowSql = false;
        }
    }

    {
        String property = properties.getProperty(SYS_PROP_SLOW_SQL_LOG_LEVEL);
        if ("error".equalsIgnoreCase(property)) {
            this.slowSqlLogLevel = "ERROR";
        } else if ("warn".equalsIgnoreCase(property)) {
            this.slowSqlLogLevel = "WARN";
        } else if ("info".equalsIgnoreCase(property)) {
            this.slowSqlLogLevel = "INFO";
        } else if ("debug".equalsIgnoreCase(property)) {
            this.slowSqlLogLevel = "DEBUG";
        }
    }
}
复制代码

以及一个专用MergeStatFilter继承了StatFilter,

public class MergeStatFilter extends StatFilter {

    public MergeStatFilter(){
        super.setMergeSql(true);
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) {
        return iface == MergeStatFilter.class || iface == StatFilter.class;
    }

    @SuppressWarnings("unchecked")
    public <T> T unwrap(Class<T> iface) {
        if (iface == MergeStatFilter.class || iface == StatFilter.class) {
            return (T) this;
        }
        return null;
    }
}
复制代码

总结: 今天实现了一个基于druid的monitor的demo,然后初步了解了一下monitor的实现的部分代码。

Guess you like

Origin juejin.im/post/7031547884344442887