1. 程式人生 > >Spring Boot整合Druid實現資料來源管理與監控

Spring Boot整合Druid實現資料來源管理與監控

1. 引言

在程式設計師的日常工作中, 經常需要編寫資料庫操作相關的程式,而這就需要資料連線池中介軟體用於管理資料庫連線。資料庫連線池負責分配、管理和釋放資料庫連線,它允許應用程式重複使用一個現有的資料庫連線,而不是再重新建立一個;釋放空閒時間超過最大空閒時間的資料庫連線來避免因為沒有釋放資料庫連線而引起的資料庫連線遺漏。通過資料庫連線池能明顯提高對資料庫操作的效能。在Java應用程式開發中,常用的連線池有DBCP、C3P0、Proxool等。

而在Spring Boot開發框架下,它預設提供了若干種可用的連線池,其中包括Druid連線池(來自於阿里系的一個開源連線池),除在連線池之外,Druid還提供了非常優秀的資料庫監控和擴充套件功能。在此,根據專案實踐中的應用,講解如何實現Spring Boot與Druid連線池的整合。

1.1 環境準備

  • JDK 1.8
  • Spring Boot 2.0.0.RELEASE
  • MySQL 5.7
  • Druid 1.1.9

1.2 Druid介紹

Druid是阿里開源的一個JDBC應用元件, 其包括三部分:

  • DruidDriver 代理Driver,能夠提供基於Filter-Chain模式的外掛體系。
  • DruidDataSource 高效可管理的資料庫連線池。
  • SQLParser SQL語法分析

通過Druid連線池中介軟體, 我們可以實現:

  • 可以監控資料庫訪問效能,Druid內建提供了一個功能強大的StatFilter外掛,能夠詳細統計SQL的執行效能,這對於線上分析資料庫訪問效能有幫助。
  • 替換傳統的DBCP和C3P0連線池中介軟體。Druid提供了一個高效、功能強大、可擴充套件性好的資料庫連線池。
  • 資料庫密碼加密。直接把資料庫密碼寫在配置檔案中,這是不好的行為,容易導致安全問題。DruidDruiver和DruidDataSource都支援PasswordCallback。
  • SQL執行日誌,Druid提供了不同的LogFilter,能夠支援Common-Logging、Log4j和JdkLog,你可以按需要選擇相應的LogFilter,監控你應用的資料庫訪問情況。
  • 擴充套件JDBC,如果你要對JDBC層有程式設計的需求,可以通過Druid提供的Filter-Chain機制,很方便編寫JDBC層的擴充套件外掛。

關於Druid的更多詳細資訊可以參考Druid官方文件

2. 配置Druid連線池

(1) 新增Maven依賴

<dependencies>
      ... 此處省略其他配置 ...
      <!-- Configuration Module -->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
      </dependency>
      <!-- MySQL Driver -->
      <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.45</version>
            <scope>runtime</scope>
      </dependency>
      <!-- Druid Pool -->
      <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.9</version>
      </dependency>
      <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.9</version>
      </dependency>
</dependencies>

(2) Spring Boot配置檔案配置

Spring Boot配置檔案有application.properties和application.yml兩種配置檔案方式 , 此處採用的是application.yml的配置方式。

# Spring Datasource Settings
spring:
  datasource:
    name: druidDataSource
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://192.168.202.17:3306/auth_service?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false
      username: root
      password: 123456
      filters: stat,wall,log4j,config
      max-active: 100
      initial-size: 1
      max-wait: 60000
      min-idle: 1
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: select 'x'
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      pool-prepared-statements: true
      max-open-prepared-statements: 50
      max-pool-prepared-statement-per-connection-size: 20

說明:
- spring.datasource.druid.max-active 最大連線數
- spring.datasource.druid.initial-size 初始化大小
- spring.datasource.druid.min-idle 最小連線數
- spring.datasource.druid.max-wait 獲取連線等待超時時間
- spring.datasource.druid.time-between-eviction-runs-millis 間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒
- spring.datasource.druid.min-evictable-idle-time-millis 一個連線在池中最小生存的時間,單位是毫秒
- spring.datasource.druid.filters=config,stat,wall,log4j 配置監控統計攔截的filters,去掉後監控介面SQL無法進行統計,’wall’用於防火牆

Druid提供以下幾種Filter資訊:

Filter類名 別名
default com.alibaba.druid.filter.stat.StatFilter
stat com.alibaba.druid.filter.stat.StatFilter
mergeStat com.alibaba.druid.filter.stat.MergeStatFilter
encoding com.alibaba.druid.filter.encoding.EncodingConvertFilter
log4j com.alibaba.druid.filter.logging.Log4jFilter
log4j2 com.alibaba.druid.filter.logging.Log4j2Filter
slf4j com.alibaba.druid.filter.logging.Slf4jLogFilter
commonlogging com.alibaba.druid.filter.logging.CommonsLogFilter
wall com.alibaba.druid.wall.WallFilter

(3) Druid配置資訊定製

通過Druid-Spring-Boot-Starter可以自動完成相關的配置, 而無須自定義配置檔案, 具體參考Druid-Spring-Boot-Starter

通過Druid-Spring-Boot-Starter的Spring Boot配置資訊示例:

spring:
  datasource:
    name: druidDataSource
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.202.17:3306/auth_service?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false
    username: root
    password: 123456
    druid:
      filters: stat,wall,log4j,config
      max-active: 100
      initial-size: 1
      max-wait: 60000
      min-idle: 1
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: select 'x'
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      pool-prepared-statements: true
      max-open-prepared-statements: 50
      max-pool-prepared-statement-per-connection-size: 20

在此, 主要通過定製的配置檔案對Druid進行自定義屬性配置, 配置檔案如下:

package com.garyond.hurricane.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "spring.datasource.druid")
public class DruidDataSourceProperties {

    private String driverClassName;
    private String url;
    private String username;
    private String password;

    private int initialSize;
    private int minIdle;
    private int maxActive = 100;
    private long maxWait;
    private long timeBetweenEvictionRunsMillis;
    private long minEvictableIdleTimeMillis;
    private String validationQuery;
    private boolean testWhileIdle;
    private boolean testOnBorrow;
    private boolean testOnReturn;
    private boolean poolPreparedStatements;
    private int maxPoolPreparedStatementPerConnectionSize;
    private String filters;

    public int getInitialSize() {
        return initialSize;
    }

    public void setInitialSize(int initialSize) {
        this.initialSize = initialSize;
    }

    public int getMinIdle() {
        return minIdle;
    }

    public void setMinIdle(int minIdle) {
        this.minIdle = minIdle;
    }

    public int getMaxActive() {
        return maxActive;
    }

    public void setMaxActive(int maxActive) {
        this.maxActive = maxActive;
    }

    public long getMaxWait() {
        return maxWait;
    }

    public void setMaxWait(long maxWait) {
        this.maxWait = maxWait;
    }

    public long getTimeBetweenEvictionRunsMillis() {
        return timeBetweenEvictionRunsMillis;
    }

    public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) {
        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
    }

    public long getMinEvictableIdleTimeMillis() {
        return minEvictableIdleTimeMillis;
    }

    public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) {
        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
    }

    public String getValidationQuery() {
        return validationQuery;
    }

    public void setValidationQuery(String validationQuery) {
        this.validationQuery = validationQuery;
    }

    public boolean isTestWhileIdle() {
        return testWhileIdle;
    }

    public void setTestWhileIdle(boolean testWhileIdle) {
        this.testWhileIdle = testWhileIdle;
    }

    public boolean isTestOnBorrow() {
        return testOnBorrow;
    }

    public void setTestOnBorrow(boolean testOnBorrow) {
        this.testOnBorrow = testOnBorrow;
    }

    public boolean isTestOnReturn() {
        return testOnReturn;
    }

    public void setTestOnReturn(boolean testOnReturn) {
        this.testOnReturn = testOnReturn;
    }

    public boolean isPoolPreparedStatements() {
        return poolPreparedStatements;
    }

    public void setPoolPreparedStatements(boolean poolPreparedStatements) {
        this.poolPreparedStatements = poolPreparedStatements;
    }

    public int getMaxPoolPreparedStatementPerConnectionSize() {
        return maxPoolPreparedStatementPerConnectionSize;
    }

    public void setMaxPoolPreparedStatementPerConnectionSize(int maxPoolPreparedStatementPerConnectionSize) {
        this.maxPoolPreparedStatementPerConnectionSize = maxPoolPreparedStatementPerConnectionSize;
    }

    public String getFilters() {
        return filters;
    }

    public void setFilters(String filters) {
        this.filters = filters;
    }

    public String getDriverClassName() {
        return driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}

(3) 配置Druid相關的Servlet和Filter

package com.garyond.hurricane.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.sql.SQLException;

@Configuration
@EnableConfigurationProperties({DruidDataSourceProperties.class})
public class DruidConfig {
    @Autowired
    private DruidDataSourceProperties properties;

    @Bean
    @ConditionalOnMissingBean
    public DataSource druidDataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(properties.getDriverClassName());
        druidDataSource.setUrl(properties.getUrl());
        druidDataSource.setUsername(properties.getUsername());
        druidDataSource.setPassword(properties.getPassword());
        druidDataSource.setInitialSize(properties.getInitialSize());
        druidDataSource.setMinIdle(properties.getMinIdle());
        druidDataSource.setMaxActive(properties.getMaxActive());
        druidDataSource.setMaxWait(properties.getMaxWait());
        druidDataSource.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRunsMillis());
        druidDataSource.setMinEvictableIdleTimeMillis(properties.getMinEvictableIdleTimeMillis());
        druidDataSource.setValidationQuery(properties.getValidationQuery());
        druidDataSource.setTestWhileIdle(properties.isTestWhileIdle());
        druidDataSource.setTestOnBorrow(properties.isTestOnBorrow());
        druidDataSource.setTestOnReturn(properties.isTestOnReturn());
        druidDataSource.setPoolPreparedStatements(properties.isPoolPreparedStatements());
        druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(properties.getMaxPoolPreparedStatementPerConnectionSize());

        try {
            druidDataSource.setFilters(properties.getFilters());
            druidDataSource.init();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return druidDataSource;
    }

    /**
     * 註冊Servlet資訊, 配置監控檢視
     *
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    public ServletRegistrationBean druidServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");

        //白名單:
        servletRegistrationBean.addInitParameter("allow","192.168.6.195");
        //IP黑名單 (存在共同時,deny優先於allow) : 如果滿足deny的話提示:Sorry, you are not permitted to view this page.
        servletRegistrationBean.addInitParameter("deny","192.168.6.73");
        //登入檢視資訊的賬號密碼, 用於登入Druid監控後臺
        servletRegistrationBean.addInitParameter("loginUsername", "admin");
        servletRegistrationBean.addInitParameter("loginPassword", "admin");
        //是否能夠重置資料.
        servletRegistrationBean.addInitParameter("resetEnable", "true");
        return servletRegistrationBean;

    }

    /**
     * 註冊Filter資訊, 監控攔截器
     *
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }
}

注:
- @EnableConfigurationProperties({DruidDataSourceProperties.class}) 用於匯入上一步Druid的配置資訊
- public ServletRegistrationBean druidServlet() 相當於Web Servlet配置
- public FilterRegistrationBean filterRegistrationBean() 相當於Web Filter配置

如果不使用上述的Servlet和Filter配置, 也可以通過下述監控器配置實現:

配置監控攔截器(相當於FilterRegistrationBean)

/**   
 * 配置監控攔截器, druid監控攔截器   
 * @ClassName: DruidStatFilter    
 * @author garyond
 * @date 2018年4月24日
 */    
@WebFilter(filterName="druidWebStatFilter",    
urlPatterns="/*",    
initParams={    
    @WebInitParam(name="exclusions",value="*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"),// 忽略資源    
})   
public class DruidStatFilter extends WebStatFilter {  

}  

配置Druid監控檢視(相當於ServletRegistrationBean)

/**   
 * druid監控檢視配置   
 *
 * @ClassName: DruidStatViewServlet    
 * @author garyond  
 * @date 2018年4月24日 
 */    
@WebServlet(urlPatterns = "/druid/*", initParams={    
    @WebInitParam(name="allow",value="192.168.6.195"),// IP白名單 (沒有配置或者為空,則允許所有訪問)    
    @WebInitParam(name="deny",value="192.168.6.73"),// IP黑名單 (存在共同時,deny優先於allow)    
    @WebInitParam(name="loginUsername",value="admin"),// 使用者名稱    
    @WebInitParam(name="loginPassword",value="admin"),// 密碼    
    @WebInitParam(name="resetEnable",value="true")// 禁用HTML頁面上的“Reset All”功能    
})   
public class DruidStatViewServlet extends StatViewServlet {  
    private static final long serialVersionUID = 7359758657306626394L;  
}  

3. 檢視Druid監控

配置完成後, 並完成相關的資料庫操作配置, 啟動Spring Boot應用程式,就可以通過訪問: http://localhost:8080/druid/index.html 訪問Druid監控後臺頁面。

Druid訪問介面

輸入使用者名稱和密碼可以檢視Druid監控資訊:

首頁

SQL監控

至此, Spring Boot整合Druid連線池已配置完畢。