Druid是由阿里巴巴数据库事业部出品的开源项目。它除了是一个高性能数据库连接池之外,更是一个自带监控的数据库连接池。虽然Springboot 2.0默认选择的数据库连接池HikariCP已经很优秀,但是对于国内用户来说,可能对于Druid更为熟悉。甚至可以说如何在Spring Boot中使用Druid是后端开发人员必须要掌握的基本技能。

和其他用于Spring Boot的项目类似,Druid提供了Druid Spring Boot Starter 用于帮助你在Spring Boot项目中轻松集成Druid数据库连接池和监控。

7.1 连接池配置与使用

7.1.1 配置项目依赖

在 Spring Boot 项目中加入druid-spring-boot-starter依赖

  1. <dependency>
  2. <groupId>com.alibaba</groupId>
  3. <artifactId>druid-spring-boot-starter</artifactId>
  4. <version>1.2.8</version>
  5. </dependency>

你可以在 Maven Repository 查询它的最新版本

7.1.2 切换Spring Boot数据源类型

Spring Boot默认的数据源是:

  • 2.0之前:org.apache.tomcat.jdbc.pool.DataSource
  • 2.0及之后: com.zaxxer.hikari.HikariDataSource

在Spring Boot中切换数据源非常简单,只需要在配置文件中指定 spring.datasource.type为我们指定的类型就好了。例如

  1. spring:
  2. datasource:
  3. type: com.alibaba.druid.pool.DruidDataSource

这样就完成切换我们的数据源为Druid。这个type的值是DataSource的接口的实现。

7.1.3 配置连接池参数

Druid Spring Boot Starter 可以使用spring.datasource.druid独立配置JDBC参数

spring:
  datasource:
    druid:
      url: jdbc:mysql:
      username: 
      password: 
      driver-class-name:

也可以不单独定义而使用spring.datasource的设置

要用好一个数据源,就要对其连接池做好相应的配置,比如下面这样:

spring:
  datasource:
    druid:
      initialSize: 10
      maxActive: 20
      maxWait: 60000
      minIdle: 1
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000
      testWhileIdle: true
      testOnBorrow: true
      testOnReturn: false
      poolPreparedStatements: true
      maxOpenPreparedStatements: 20
      validationQuery: SELECT 1
      validation-query-timeout: 500
      filters: stat

Druid Spring Boot Starter 不仅限于对以上配置属性提供支持,[DruidDataSource](https://github.com/alibaba/druid/blob/master/src/main/java/com/alibaba/druid/pool/DruidDataSource.java) 内提供setter方法的可配置属性都将被支持。你可以参考WIKI文档或通过IDE输入提示来进行配置。

7.1.4 验证新连接池

到这一步,就已经完成了将Spring Boot的默认数据源切换到Druid的所有操作。运行项目
image.png
我们从控制台信息中可以看到Druid已经成功应用
image.png

7.2 配置与使用Druid监控

既然用了Druid,那么对于Druid的监控功能怎么能不用一下呢?下面就来再进一步做一些配置,来启用Druid的监控。

7.2.1 配置监控参数

application.yml中添加Druid的监控配置。

spring:
  datasource:
    druid:
      statViewServlet:
        enabled: true
        url-pattern: /druid/*
        reset-enable: true
        login-username: admin
        login-password: admin
        allow: 
        deny:

是否启用StatViewServlet(监控页面)默认值为false(考虑到安全问题默认并未启动,如需启用建议设置密码或白名单以保障安全)
上面的配置主要用于开启stat监控统计的界面以及监控内容的相关配置,具体释意如下:

  • spring.datasource.druid.stat-view-servlet.url-pattern:访问地址规则
  • spring.datasource.druid.stat-view-servlet.reset-enable:是否允许清空统计数据
  • spring.datasource.druid.stat-view-servlet.login-username:监控页面的登录账户
  • spring.datasource.druid.stat-view-servlet.login-password:监控页面的登录密码

针对之前实现的内容,我们创建一个Controller来通过接口去调用数据访问操作(因为逻辑简单,我们忽略了Service层代码的编写):

package com.longser.union.cloud.controller;

import com.longser.union.cloud.data.mapper.UserMapper;
import com.longser.union.cloud.data.model.UserEntry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

    private final UserMapper userMapper;

    @Autowired
    public TestController(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @GetMapping("/test/first")
    public UserEntry getFirst() {
        return  userMapper.getOne(1L);
    }
}

7.2.3 访问监控页面

完成上面所有配置之后,启动应用,访问Druid的监控页面http://localhost:8088/druid/,可以看到如下登录页面:
7. 使用数据库连接池Druid - 图3
输入上面spring.datasource.druid.stat-view-servlet.login-username和spring.datasource.druid.stat-view-servlet.login-password配置的登录账户与密码,就能看到如下监控页面:
image.png
进入到这边时候,就可以看到对于应用端而言的各种监控数据了。这里讲解几个最为常用的监控页面:

数据源:这里可以看到之前我们配置的数据库连接池信息以及当前使用情况的各种指标。
7. 使用数据库连接池Druid - 图5
SQL监控:该数据源中执行的SQL语句极其统计数据。在这个页面上,我们可以很方便的看到当前这个Spring Boot都执行过哪些SQL,这些SQL的执行频率和执行效率也都可以清晰的看到。如果你这里没看到什么数据?别忘了我们之前创建了一个Controller,用这些接口可以触发对数据库的操作。所以,这里我们可以通过在另外一个浏览器窗口中访问http://localhost:8088/test/first去触发操作,这样SQL监控页面就会产生一些数据:
image.png
图中监控项上,执行时间、读取行数、更新行数都通过区间分布的方式表示,将耗时分布成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秒以上的次数

记录耗时区间的发生次数,通过区分分布,可以很方便看出SQL运行的极好、普通和极差的分布。 耗时区分分布提供了“执行+RS时分布”,是将执行时间+ResultSet持有时间合并监控,这个能方便诊断返回行数过多的查询。

SQL防火墙:该页面记录了与SQL监控不同维度的监控数据,更多用于对表访问维度、SQL防御维度的统计。
7. 使用数据库连接池Druid - 图7
该功能数据记录的统计需要在spring.datasource.druid.filters中增加wall属性才会进行记录统计,比如这样:

spring:
  datasource:
    druid:
      filters: stat,wall

注意:这里的所有监控信息是对这个应用实例的数据源而言的,而并不是数据库全局层面的,可以视为应用层的监控,不可能作为中间件层的监控。

7.2.4 获取监控数据

Druid 的监控数据可以在开启 StatFilter 后通过 DruidStatManagerFacade 进行获取,获取到监控数据之后你可以将其暴露给你的监控系统进行使用。Druid 默认的监控系统数据也来源于此。下面给做一个简单的演示,在 Spring Boot 中如何通过 HTTP 接口将 Druid 监控数据以 JSON 的形式暴露出去,实际使用中你可以根据你的需要自由地对监控数据、暴露方式进行扩展。

package com.longser.union.cloud.controller;

import com.alibaba.druid.stat.DruidStatManagerFacade;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DruidStatController {
    @GetMapping("/test/druid/stat")
    public Object druidStat(){
        /*
         该方法可以获取所有数据源的监控数据,除此之外 DruidStatManagerFacade 
         还提供了一些其他方法,你可以按需选择使用。
        */
        return DruidStatManagerFacade.getInstance().getDataSourceStatDataList();
    }
}

重启应用后访问 http://localhost:8088/test/druid/stat,可以如得到如下的信息(为了友获得好可读的 JSON 信息,你可以使用重要的接口调试工具 Postman)

[
    {
        "Identity": 1157484092,
        "Name": "DataSource-1157484092",
        "DbType": "mysql",
        "DriverClassName": "org.mariadb.jdbc.Driver",
        "URL": "jdbc:mysql:******",
        "UserName": "*****",
        "FilterClassNames": [
            "com.alibaba.druid.filter.stat.StatFilter",
            "com.alibaba.druid.wall.WallFilter"
        ],
        "WaitThreadCount": 0,
        "NotEmptyWaitCount": 0,
        "NotEmptyWaitMillis": 0,
        "PoolingCount": 10,
        "PoolingPeak": 10,
        "PoolingPeakTime": "2021-11-02T06:22:01.424+00:00",
        "ActiveCount": 0,
        "ActivePeak": 0,
        "ActivePeakTime": null,
        "InitialSize": 10,
        "MinIdle": 1,
        "MaxActive": 20,
        "QueryTimeout": 0,
        "TransactionQueryTimeout": 0,
        "LoginTimeout": 0,
        "ValidConnectionCheckerClassName": null,
        "ExceptionSorterClassName": null,
        "TestOnBorrow": true,
        "TestOnReturn": false,
        "TestWhileIdle": true,
        "DefaultAutoCommit": true,
        "DefaultReadOnly": null,
        "DefaultTransactionIsolation": null,
        "LogicConnectCount": 0,
        "LogicCloseCount": 0,
        "LogicConnectErrorCount": 0,
        "PhysicalConnectCount": 10,
        "PhysicalCloseCount": 0,
        "PhysicalConnectErrorCount": 0,
        "DiscardCount": 0,
        "ExecuteCount": 0,
        "ExecuteUpdateCount": 0,
        "ExecuteQueryCount": 0,
        "ExecuteBatchCount": 0,
        "ErrorCount": 0,
        "CommitCount": 0,
        "RollbackCount": 0,
        "PSCacheAccessCount": 0,
        "PSCacheHitCount": 0,
        "PSCacheMissCount": 0,
        "StartTransactionCount": 0,
        "TransactionHistogram": [
            0,
            0,
            0,
            0,
            0,
            0,
            0
        ],
        "ConnectionHoldTimeHistogram": [
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            0
        ],
        "RemoveAbandoned": false,
        "ClobOpenCount": 0,
        "BlobOpenCount": 0,
        "KeepAliveCheckCount": 0,
        "KeepAlive": false,
        "FailFast": false,
        "MaxWait": 60000,
        "MaxWaitThreadCount": -1,
        "PoolPreparedStatements": true,
        "MaxPoolPreparedStatementPerConnectionSize": 20,
        "MinEvictableIdleTimeMillis": 300000,
        "MaxEvictableIdleTimeMillis": 25200000,
        "LogDifferentThread": true,
        "RecycleErrorCount": 0,
        "PreparedStatementOpenCount": 0,
        "PreparedStatementClosedCount": 0,
        "UseUnfairLock": false,
        "InitGlobalVariants": false,
        "InitVariants": false
    }
]

7.3 连接池配置参数说明

关于Druid中各连接池配置的说明可查阅下面的表格:

配置 缺省值 说明
name
配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。如果没有配置,将会生成一个名字,格式是:”DataSource-“ + System.identityHashCode(this). 另外配置此属性至少在1.0.5版本中是不起作用的,强行设置name会出错。详情-点此处
url
连接数据库的url,不同数据库不一样。
username
连接数据库的用户名
password
连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里
driverClassName 根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName
initialSize 0 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive 8 最大连接池数量
maxIdle 8 已经不再使用,配置了也没效果
minIdle
最小连接池数量
maxWait
获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatements false 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxPoolPreparedStatementPerConnectionSize -1 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery
用来检测连接是否有效的sql,要求是一个查询语句,常用select ‘x’。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
validationQueryTimeout
单位:秒,检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法
testOnBorrow true 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturn false 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testWhileIdle false 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
keepAlive false 连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作。
timeBetweenEvictionRunsMillis 1分钟 有两个含义: 1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。 2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun 30分钟 不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis
连接保持空闲而不被驱逐的最小时间
connectionInitSqls
物理连接初始化的时候执行的sql
exceptionSorter 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters
属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall
proxyFilters
类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系

7.4 参考资料

推荐阅读下面的参考资料:

版权说明:本文由北京朗思云网科技股份有限公司原创,向互联网开放全部内容但保留所有权力。