乐趣区

关于spring:Spring系列之集成Druid连接池及监控配置

前言

前一篇文章咱们相熟了 HikariCP 连接池,也理解到它的性能很高,明天咱们讲一下另一款比拟受欢迎的连接池:Druid,这是阿里开源的一款数据库连接池,它官网上宣称:为监控而生!他能够实现页面监控,看到 SQL 的执行次数、工夫和慢 SQL 信息,也能够对数据库明码信息进行加密,也能够对监控后果进行日志的记录,以及能够实现对敏感操作实现开关,杜绝 SQL 注入,上面咱们具体讲一下它如何与 Spring 集成,并且顺便理解一下它的监控的配置。

文章要点:

  • Spring 集成 Druid
  • 监控 Filters 配置(stat、wall、config、log)
  • HiKariCP 和 Druid 该如何抉择

如何集成 Druid

1、减少相干依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.6</version>
        </dependency>

2、配置 DataSource

@Configuration
public class DataSourceConfiguration {@ConfigurationProperties(prefix = "spring.datasource.druid")
    @Bean
    public DataSource dataSource(){return new DruidDataSource();
    }
}

3、配置项参数 application.properties

# 或 spring.datasource.url
spring.datasource.druid.url=jdbc:mysql://localhost:3306/chenrui
# 或 spring.datasource.username
spring.datasource.druid.username=root
# 或 spring.datasource.password
spring.datasource.druid.password=root

#初始化时建设物理连贯的个数。初始化产生在显示调用 init 办法,或者第一次 getConnection 时
spring.datasource.druid.initial-size=5
#最大连接池数量
spring.datasource.druid.max-active=20
#最小连接池数量
spring.datasource.druid.min-idle=5
#获取连贯时最大等待时间,单位毫秒。配置了 maxWait 之后,缺省启用偏心锁,并发效率会有所降落,如果须要能够通过配置 useUnfairLock 属性为 true 应用非偏心锁
spring.datasource.druid.max-wait=500
#是否缓存 preparedStatement,也就是 PSCache。PSCache 对反对游标的数据库性能晋升微小,比如说 oracle。在 mysql 下倡议敞开。spring.datasource.druid.pool-prepared-statements=false
#要启用 PSCache,必须配置大于 0,当大于 0 时,poolPreparedStatements 主动触发批改为 true。在 Druid 中,不会存在 Oracle 下 PSCache 占用内存过多的问题,能够把这个数值配置大一些,比如说 100
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=-1
#用来检测连贯是否无效的 sql,要求是一个查问语句,罕用 select 'x'。如果 validationQuery 为 null,testOnBorrow、testOnReturn、testWhileIdle 都不会起作用。spring.datasource.druid.validation-query=select 'x'
#单位:秒,检测连贯是否无效的超时工夫。底层调用 jdbc Statement 对象的 void setQueryTimeout(int seconds) 办法
spring.datasource.druid.validation-query-timeout=1
#申请连贯时执行 validationQuery 检测连贯是否无效,做了这个配置会升高性能。spring.datasource.druid.test-on-borrow=true
#偿还连贯时执行 validationQuery 检测连贯是否无效,做了这个配置会升高性能。spring.datasource.druid.test-on-return=true
#倡议配置为 true,不影响性能,并且保障安全性。申请连贯的时候检测,如果闲暇工夫大于 timeBetweenEvictionRunsMillis,执行 validationQuery 检测连贯是否无效
spring.datasource.druid.test-while-idle=true
#有两个含意:默认 1 分钟
#1) Destroy 线程会检测连贯的间隔时间,如果连贯闲暇工夫大于等于 minEvictableIdleTimeMillis 则敞开物理连贯。#2) testWhileIdle 的判断根据,具体看 testWhileIdle 属性的阐明
spring.datasource.druid.time-between-eviction-runs-millis=60000
# 连贯放弃闲暇而不被驱赶的最小工夫
spring.datasource.druid.min-evictable-idle-time-millis=600000
# 连贯放弃闲暇而不被驱赶的最大工夫
spring.datasource.druid.max-evictable-idle-time-millis=900000
#配置多个英文逗号分隔
spring.datasource.druid.filters=stat,wall

# WebStatFilter 配置
# 是否启用 StatFilter 默认值 false
spring.datasource.druid.web-stat-filter.enabled=true
# 匹配的 url
spring.datasource.druid.web-stat-filter.url-pattern=/*
# 排除一些不必要的 url,比方.js,/jslib/ 等等
spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*
# 你能够敞开 session 统计性能
spring.datasource.druid.web-stat-filter.session-stat-enable=true
# 默认 sessionStatMaxCount 是 1000 个,你也能够按须要进行配置
spring.datasource.druid.web-stat-filter.session-stat-max-count=1000
# 使得 druid 可能晓得以后的 session 的用户是谁
spring.datasource.druid.web-stat-filter.principal-session-name=cross
# 如果你的 user 信息保留在 cookie 中,你能够配置 principalCookieName,使得 druid 晓得以后的 user 是谁
spring.datasource.druid.web-stat-filter.principal-cookie-name=aniu
# 配置 profileEnable 可能监控单个 url 调用的 sql 列表
spring.datasource.druid.web-stat-filter.profile-enable=


# 配置_StatViewServlet 配置, 用于展现 Druid 的统计信息
#是否启用 StatViewServlet(监控页面)默认值为 false(思考到平安问题默认并未启动,如需启用倡议设置明码或白名单以保障平安)spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
#容许清空统计数据
spring.datasource.druid.stat-view-servlet.reset-enable=true
#监控页面登陆的用户名
spring.datasource.druid.stat-view-servlet.login-username=root
# 登陆监控页面所需的明码
spring.datasource.druid.stat-view-servlet.login-password=1234

# deny 优先于 allow,如果在 deny 列表中,就算在 allow 列表中,也会被回绝。# 如果 allow 没有配置或者为空,则容许所有拜访
#容许的 IP
# spring.datasource.druid.stat-view-servlet.allow=
#回绝的 IP
#spring.datasource.druid.stat-view-servlet.deny=127.0.0.1

#指定 xml 文件所在的地位
mybatis.mapper-locations=classpath:mapper/*Mapper.xml
#开启数据库字段和类属性的映射反对驼峰
mybatis.configuration.map-underscore-to-camel-case=true

4、代码相干

数据库脚本

create table user_info
(
    id        bigint unsigned auto_increment
        primary key,
    user_id   int          not null comment '用户 id',
    user_name varchar(64)  not null comment '实在姓名',
    email     varchar(30)  not null comment '用户邮箱',
    nick_name varchar(45)  null comment '昵称',
    status    tinyint      not null comment '用户状态,1- 失常,2- 登记,3- 解冻',
    address   varchar(128) null
)
    comment '用户根本信息';

初始化数据

INSERT INTO chenrui.user_info (id, user_id, user_name, email, nick_name, status, address) VALUES (1, 80001, '张三丰', 'xxx@126.com', '三哥', 1, '武当山');
INSERT INTO chenrui.user_info (id, user_id, user_name, email, nick_name, status, address) VALUES (2, 80002, '张无忌', 'yyy@126.com', '', 1, null);

mapper.xml 文件编写

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.springdataSourcedruid.dao.UserInfoDAO">

    <select id="findAllUser" resultType="com.example.springdataSourcedruid.entity.UserInfo">
        select * from user_info
    </select>

    <select id="getUserById" resultType="com.example.springdataSourcedruid.entity.UserInfo">
        select * from user_info where id = #{id}
    </select>

    <select id="getUserByIdEqualOne" resultType="com.example.springdataSourcedruid.entity.UserInfo">
        select * from user_info where id =1
    </select>

    <select id="getUserByIdEqualTwo" resultType="com.example.springdataSourcedruid.entity.UserInfo">
        select * from user_info where id =2
    </select>
</mapper>

编写 DAO 接口

public interface UserInfoDAO {List<UserInfo> findAllUser();

    UserInfo getUserById(@Param("id") int id);

    UserInfo getUserByIdEqualOne();

    UserInfo getUserByIdEqualTwo();}

测试 controller

@RestController
@Slf4j
public class UserInfoController {
    @Resource
    private UserInfoDAO userInfoDAO;

    @GetMapping(path = "/all")
    public List<UserInfo> getAllUser(){return userInfoDAO.findAllUser();
    }

    @GetMapping(path = "/getUser/{id}")
    public UserInfo getById(@PathVariable int id){return userInfoDAO.getUserById(id);
    }

    @GetMapping(path = "/getUser/one")
    public UserInfo getById1(){return userInfoDAO.getUserByIdEqualOne();
    }

    @GetMapping(path = "/getUser/two")
    public UserInfo getById2(){return userInfoDAO.getUserByIdEqualTwo();
    }
}

启动类

@SpringBootApplication
@MapperScan(basePackages = "com.example.springdataSourcedruid.dao")
public class SpringDataSourceDruidApplication {public static void main(String[] args) {SpringApplication.run(SpringDataSourceDruidApplication.class, args);
    }
}

5、启动验证

拜访:http://127.0.0.1:8080/druid/,弹出登陆界面,用户和明码对应咱们的配置文件中设置的用户名和明码

登陆进去能够看到外面有很多监控,这里咱们只看咱们本次所关怀的,数据源,SQL 监控,URL 监控,其余的能够自行钻研。


下面咱们看到数据源外面的信息和咱们在 application.properties 中配置的统一

上面咱们别离执行几次,咱们筹备好的验证接口
http://127.0.0.1:8080/all
http://127.0.0.1:8080/getUser/1
http://127.0.0.1:8080/getUser/2
http://127.0.0.1:8080/getUser…
http://127.0.0.1:8080/getUser…

而后看一下的各项监控信息长什么样子
SQL 监控


下面咱们看到咱们总共四个语句,以及四个语句的运行状况
SQL 监控项上,执行工夫、读取行数、更新行数都有区间散布,将耗时散布成 8 个区间:

  • 0 – 1 耗时 0 到 1 毫秒的次数
  • 1 – 10 耗时 1 到 10 毫秒的次数
  • 10 – 100 耗时 10 到 100 毫秒的次数
  • 100 – 1,000 耗时 100 到 1000 毫秒的次数
  • 1,000 – 10,000 耗时 1 到 10 秒的次数
  • 10,000 – 100,000 耗时 10 到 100 秒的次数
  • 100,000 – 1,000,000 耗时 100 到 1000 秒的次数
  • 1,000,000 – 耗时 1000 秒以上的次数

这里你可能会有疑难 ,id = 1 和 id= 2 怎么还是离开的,如果我 id 有一亿个,难道要在监控页面上有一亿条记录吗?不是应该都应该是 id=?的模式吗?这里前面会讲到,波及到 sql 合并的监控配置

URL 监控


这里能够很清晰的看到,每个 url 波及到的数据库执行的信息

druid 的内置 filters

在 druid 的 jar 中,META-INF/druid-filter.properties 中有其内置的 filter,内容如下:

druid.filters.default=com.alibaba.druid.filter.stat.StatFilter
druid.filters.stat=com.alibaba.druid.filter.stat.StatFilter
druid.filters.mergeStat=com.alibaba.druid.filter.stat.MergeStatFilter
druid.filters.counter=com.alibaba.druid.filter.stat.StatFilter
druid.filters.encoding=com.alibaba.druid.filter.encoding.EncodingConvertFilter
druid.filters.log4j=com.alibaba.druid.filter.logging.Log4jFilter
druid.filters.log4j2=com.alibaba.druid.filter.logging.Log4j2Filter
druid.filters.slf4j=com.alibaba.druid.filter.logging.Slf4jLogFilter
druid.filters.commonlogging=com.alibaba.druid.filter.logging.CommonsLogFilter
druid.filters.commonLogging=com.alibaba.druid.filter.logging.CommonsLogFilter
druid.filters.wall=com.alibaba.druid.wall.WallFilter
druid.filters.config=com.alibaba.druid.filter.config.ConfigFilter
druid.filters.haRandomValidator=com.alibaba.druid.pool.ha.selector.RandomDataSourceValidateFilter

default、stat、wall 等是 filter 的别名,能够在 application.properties 中能够通过 spring.datasource.druid.filters 属性指定别名来开启相应的 filter,也能够在 Spring 中通过属性注入形式来开启,接下来介绍一下比拟罕用的 filter

拦截器 stat(default、counter)

在 spring.datasource.druid.filters 配置中蕴含 stat,代表开启监控统计信息,在下面内容中,咱们曾经看到蕴含执行次数、工夫、最慢 SQL 等信息。也提到因为有的 sql 是非参数话的,这样会导致在监控页面有很多监控的 sql 都是一样的,只是参数不一样,咱们这时候须要将合同 sql 配置关上;
只须要在 application.properties 减少配置:

# 为监控开启 SQL 合并,将慢 SQL 的工夫定为 2 毫秒,记录慢 SQL 日志
spring.datasource.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=2;druid.stat.logSlowSql=true

看一下运行后果:

1、上面 2 个语句在监控页面被合并了:

select * from user_info where id=1
select * from user_info where id=2
// 合并后的后果是:SELECT * FROM user_info WHERE id = ?

2、超过 2ms 的语句,在监控页面红色展现进去
3、慢 SQL 在日志中会被体现进去

拦截器 mergeStat

继承 stat,根本个性和 stat 是一样的,不做延长

拦截器 encoding

因为历史起因,一些数据库保留数据的时候应用了谬误编码,须要做编码转换。
能够用上面的形式开启:

spring.datasource.druid.filters=stat,encoding
#配置客户端的编码 UTF-8,服务端的编码是 ISO-8859-1,这样存在数据库中的乱码查问进去就不是乱码了。spring.datasource.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=2;druid.stat.logSlowSql=true;clientEncoding=UTF-8;serverEncoding=ISO-8859-1

拦截器 log4j(log4j2、slf4j、commonlogging、commonLogging)

Druid 内置提供了四种 LogFilter(Log4jFilter、Log4j2Filter、CommonsLogFilter、Slf4jLogFilter),用于输入 JDBC 执行的日志

# 这里应用 log4j2 为例
spring.datasource.druid.filters=stat,log4j2
#druid.log.conn 记录连贯、druid.log.stmt 记录语句、druid.log.rs 记录后果集、druid.log.stmt.executableSql 记录可执行的 SQL
spring.datasource.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=2;druid.stat.logSlowSql=true;druid.log.conn=true;druid.log.stmt=true;druid.log.rs=true;druid.log.stmt.executableSql=true
#为不便验证,咱们开启以下 loggerName 为 DEBUG
logging.level.druid.sql.Statement=DEBUG
logging.level.druid.sql.ResultSet=DEBUG
logging.level.druid.sql.Connection=DEBUG
logging.level.druid.sql.DataSource=DEBUG


咱们能够看到执行 SQL 的整个过程,开启连贯 > 从连接池获取一个连贯 > 组装 SQL 语句 > 执行 > 后果集返回 > 连接池回收连贯
这里只用了 log4j2 这一种类型,其余能够自行去验证。

拦截器 wall

WallFilter 的性能是进攻 SQL 注入攻打。它是基于 SQL 语法分析,了解其中的 SQL 语义,而后做解决的,智能,精确,误报率低。缩小危险的产生,wall 拦截器还是很重要的。比如说不容许应用 truncate,不容许物理删除,这时候 wall 就用得上了。配置形式有两种:

第一种:缺省配置

spring.datasource.druid.filters=stat,wall,log4j2

这种配置是默认配置,而且大多数都不会拦挡,可能不合乎特定的场景,默认属性值参照:https://www.bookstack.cn/read…

第二种:属性指定配置

这种形式的益处是:咱们能够针对特定场景进行限定,比如说不能用存储过程,不能物理删除,是否容许语句中有正文等等。

// 在 DruidDataSource 生成前注入 WallFilter
@ConfigurationProperties(prefix = "spring.datasource.druid")
    @Bean
    public DataSource dataSource(){DruidDataSource dataSource = new DruidDataSource();
        dataSource.getProxyFilters().add(wallFilter());
        return dataSource;
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.filter.wall.config")
    public WallConfig wallConfig(){return new WallConfig();
    }
    @Bean
    public WallFilter wallFilter(){WallFilter filter = new WallFilter();
        filter.setConfig(wallConfig());
        filter.setDbType("mysql");
        return filter;
    }
# 不容许物理删除语句
spring.datasource.druid.filter.wall.config.delete-allow=false

执行一下试试成果:

能够看到日志显示,不容许删除,这样能够防止一些同学不依照公司开发标准来开发代码,缩小危险。其余配置本人能够试验一下。

拦截器 Config

Config 作用:从配置文件中读取配置;从近程 http 文件中读取配置;为数据库明码提供加密性能
实际上前两项作用意义不大,最要害的是第三项作用,因为数据库明码间接写在配置中,对运维平安来说,是一个很大的挑战。Druid 为此提供一种数据库明码加密的伎俩 ConfigFilter
如何应用:

# 在 application.properties 的链接属性配置项中减少 config.file,能够是本地文件,也能够是近程文件,比方 config.file=http://127.0.0.1/druid-pool.properties
spring.datasource.druid.connection-properties=config.file=file:///Users/chenrui/druid-pool.properties

加密咱们的数据库明码

应用上面的命令生成数据库明码的密文和秘钥对

java -cp druid-1.0.16.jar com.alibaba.druid.filter.config.ConfigTools you_password

druid-pool.properties 文件内容

数据库明码配置项的值改为密文

spring.datasource.druid.password=kPYuT1e6i6M929mNvKfvhyBx3eCI+Fs0pqA3n7GQYIoo76OaWVg3KALr7EdloivFVBSeF0zo5IGIfpbGtAKa+Q==

本人启动一下试试,发现一切正常,信息安全问题也解决了。

Druid 和 HikariCP 如何抉择

网络上有这么一个图,能够看到 Druid 是和其申明的统一(为监控而生),然而目前市面上有很多监控相干的中间件和技术,HikariCP 能够通过这些技术补救监控方面的有余

HikariCP 则说本人是性能最好的连接池,然而 Druid 也禁受住了阿里双 11 的大考,实际上性能也是很好的

抉择哪一款就见仁见智了,不过两款都是开源产品,阿里的 Druid 有中文的开源社区,交换起来更加不便,并且通过阿里多个零碎的试验,想必也是十分的稳固,而 Hikari 是 SpringBoot2.0 默认的连接池,全世界应用范畴也十分广,对于大部分业务来说,应用哪一款都是差不多的,毕竟性能瓶颈个别都不在连接池。大家可依据本人的爱好自由选择

退出移动版