Spring Boot (四): Druid 鏈接池密碼加密與監控

在上一篇文章《Spring Boot (三): ORM 框架 JPA 與鏈接池 Hikari》 咱們介紹了 JPA 與鏈接池 Hikari 的整合使用,在國內使用比較多的鏈接池還有一個是阿里開源的 Druid 。本篇文章咱們就來聊一聊 Druid 的一些使用姿式。css

1. Druid 是什麼?

咱們先來看一下官方的回答:java

Druid 是 Java 語言中最好的數據庫鏈接池。 Druid 可以提供強大的監控和擴展功能。mysql

說 Druid 是 Java 語言中最好的數據庫鏈接池,這個筆者我的以爲有些吹牛了,至少在性能上和咱們上一篇介紹的 Hikari 是沒得比的,相關的性能測試在網上能找到不少,筆者這邊就不列舉了。可是 Druid 在其餘的一些方面就作的比較出色了,功能很是豐富:git

  • 能夠監控數據庫訪問性能, Druid 內置提供了一個功能強大的StatFilter插件,可以詳細統計 SQL 的執行性能,這對於線上分析數據庫訪問性能有幫助。
  • 數據庫密碼加密。直接把數據庫密碼寫在配置文件中,這是很差的行爲,容易致使安全問題。 DruidDruiver 和 DruidDataSource 都支持 PasswordCallback 。
  • SQL 執行日誌, Druid 提供了不一樣的 LogFilter ,可以支持 Common-Logging 、 Log4j 和 JdkLog ,你能夠按須要選擇相應的 LogFilter ,監控你應用的數據庫訪問狀況。
  • 擴展 JDBC ,若是你要對 JDBC 層有編程的需求,能夠經過 Druid 提供的 Filter 機制,很方便編寫 JDBC 層的擴展插件。

2. Spring Boot 應用中如何使用

目前 Druid 官方爲咱們提供了兩種使用依賴方式,一種是基於傳統 Java 工程提供的依賴包, maven 座標以下:github

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.20</version>
</dependency>
複製代碼

還有一種是基於 Spring Boot 提供的依賴包, maven 座標以下:web

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.20</version>
</dependency>
複製代碼

下面的這種依賴包除了包含了上面的那種 Druid 基礎包,還包含了 Spring Boot 自動配置的依賴包以及 sl4j-api ,咱們在 Spring Boot 中使用 Druid ,固然是推薦各位讀者使用第二種方式引入依賴。spring

3. 工程實戰

3.1 建立父工程 spring-boot-jpa-druid

父工程 pom.xml 以下:sql

代碼清單:spring-boot-jpa-druid/pom.xml數據庫


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.springcloud</groupId>
    <artifactId>spring-boot-jpa-druid</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-jpa-druid</name>
    <description>spring-boot-jpa-druid</description>

    <properties>
        <druid.version>1.1.20</druid.version>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</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>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </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>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
複製代碼
  • 筆者這裏使用的 Druid 依賴包是 druid-spring-boot-starter ,版本號爲 1.1.20。

3.2 數據庫密碼不加密的配置文件 application-pass.yml 以下:

代碼清單:spring-boot-jpa-druid/src/main/resources/application-pass.ymlapache


spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
    username: root
    password: 123456
    driverClassName: com.mysql.cj.jdbc.Driver
    druid:
      # 鏈接池的配置信息
      # 初始化時創建物理鏈接的個數
      initial-size: 3
      # 鏈接池最小鏈接數
      min-idle: 3
      # 鏈接池最大鏈接數
      max-active: 20
      # 獲取鏈接時最大等待時間,單位毫秒
      max-wait: 60000
      # 申請鏈接的時候檢測,若是空閒時間大於timeBetweenEvictionRunsMillis,執行validationQuery檢測鏈接是否有效。
      test-while-idle: true
      # 既做爲檢測的間隔時間又做爲testWhileIdel執行的依據
      time-between-connect-error-millis: 60000
      # 銷燬線程時檢測當前鏈接的最後活動時間和當前時間差大於該值時,關閉當前鏈接
      min-evictable-idle-time-millis: 30000
      # 用來檢測鏈接是否有效的sql 必須是一個查詢語句
      # mysql中爲 select 'x'
      # oracle中爲 select 1 from dual
      validation-query: select 'x'
      # 申請鏈接時會執行validationQuery檢測鏈接是否有效,開啓會下降性能,默認爲true
      test-on-borrow: false
      # 歸還鏈接時會執行validationQuery檢測鏈接是否有效,開啓會下降性能,默認爲true
      test-on-return: false
      # 是否緩存preparedStatement,mysql5.5+建議開啓
      pool-prepared-statements: true
      # 當值大於0時poolPreparedStatements會自動修改成true
      max-pool-prepared-statement-per-connection-size: 20
      # 合併多個DruidDataSource的監控數據
      use-global-data-source-stat: false
      # 配置擴展插件
      filters: stat,wall,slf4j
      # 經過connectProperties屬性來打開mergeSql功能;慢SQL記錄
      connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
      # 定時輸出統計信息到日誌中,並每次輸出日誌會致使清零(reset)鏈接池相關的計數器。
      time-between-log-stats-millis: 300000
      # 配置DruidStatFilter
      web-stat-filter:
        enabled: true
        url-pattern: '/*'
        exclusions: '*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*'
      # 配置DruidStatViewServlet
      stat-view-servlet:
        # 是否啓用StatViewServlet(監控頁面)默認值爲false(考慮到安全問題默認並未啓動,如需啓用建議設置密碼或白名單以保障安全)
        enabled: true
        url-pattern: '/druid/*'
        # IP白名單(沒有配置或者爲空,則容許全部訪問)
        allow: 127.0.0.1,192.168.0.1
        # IP黑名單 (存在共同時,deny優先於allow)
        deny: 192.168.0.128
        # 禁用HTML頁面上的「Reset All」功能
        reset-enable: false
        # 登陸名
        login-username: admin
        # 登陸密碼
        login-password: admin
複製代碼
  • 相關配置的含義已經寫在註釋中了,這裏有一點要講一下,當咱們要配置統計信息(包括監控信息) time-between-log-stats-millis 輸出至日誌中,合併多個DruidDataSource的監控數據 use-global-data-source-stat 不可開啓,不然啓動會報錯。
  • spring.datasource.druid.filters :由於 Druid 的擴展是經過 Filter 插件的形式來開啓的,這裏咱們開啓了 statwall ,這倆個分別爲監控和防護 SQL 注入攻擊。 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

從名稱上能夠看出來,主要是一些編碼和日誌的相關 Filter 。

3.3 數據庫密碼加密

在生產環境中,直接在配置文件中暴露明文密碼是一件很是危險的事情,出於兩點考慮:對外,即便應用服務被入侵,數據庫仍是安全的;對內,生產環境的數據庫密碼理論上應該只有 dba 知道,可是代碼都是在代碼倉庫中放着的,若是密碼沒有加密,每次發佈前 dba 都須要手動修改配置文件後再進行打包編譯。

首先,咱們須要生成數據庫密碼的密文,須要在命令行中執行以下命令:

java -cp druid-1.0.16.jar com.alibaba.druid.filter.config.ConfigTools you_password
複製代碼

輸出以下:

privateKey:MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAh12hnaZuMe76Yb4pi7ogSAEMOcavmz7Blo8DYxeipxeZQhnrXngxc0gAQ6ORlofLWtDm6S7bI7wfDT2EFy/2DwIDAQABAkABMRjYK3vy4pi/vY3eFhBssd2qsI4hPsczjSTJfY7IC9Dc1f7g0axTM6Cx68tRUwv0rSnUiJ5EcDEhuD0JusSZAiEAwX1HpCTq8QgBV1WriHQC7Cd/9Qqp1V4yJeA/jdvXhbsCIQCzGS6wdTQCXDZKLvjRLeSUyTmmIqV/wckqdnpMUZ2BvQIgBIamr1tBt6OlTGKvoYB9NQLzhkrakCgk6ifltK7IytMCIBIbf67zipiafhqt+RYdD7lDRwLXCeiKzS3v4JmKvuP5AiEAr+zqD6sdXv7rWjqu50n+LXbWtNP/M4JzzO1mJOHEhoE=

publicKey:MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIddoZ2mbjHu+mG+KYu6IEgBDDnGr5s+wZaPA2MXoqcXmUIZ6154MXNIAEOjkZaHy1rQ5uku2yO8Hw09hBcv9g8CAwEAAQ==

password:Y464AerH8tabxQg5DlkUej6gQ64KY73ahgiPyaB0vguLBLjUEEkVu6VBueiXxcnMfVjh1Nbd+lJNUTnS1a3/xg==
複製代碼

這裏咱們須要將生成的公鑰 publicKey 和密碼 password 加入配置文件中, application-decrypt.yml 以下:

代碼清單:spring-boot-jpa-druid/src/main/resources/application-decrypt.yml


spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
    username: root
    # 加密後密文,原密碼爲 123456
    password: Y464AerH8tabxQg5DlkUej6gQ64KY73ahgiPyaB0vguLBLjUEEkVu6VBueiXxcnMfVjh1Nbd+lJNUTnS1a3/xg==
    driverClassName: com.mysql.cj.jdbc.Driver
    druid:
      filter:
        config:
          enabled: true
      connection-properties: config.decrypt=true;config.decrypt.key=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIddoZ2mbjHu+mG+KYu6IEgBDDnGr5s+wZaPA2MXoqcXmUIZ6154MXNIAEOjkZaHy1rQ5uku2yO8Hw09hBcv9g8CAwEAAQ==
    # 剩餘配置省略
複製代碼
  • 已省略部分配置,有須要的讀者能夠訪問 Github 倉庫獲取。

3.4 配置文件 application.yml 以下:

代碼清單:spring-boot-jpa-druid/src/main/resources/application.yml


server:
  port: 8080
spring:
  application:
    name: spring-boot-jpa-druid
  profiles:
    active: decrypt
  jpa:
    database: mysql
    show-sql: true
    generate-ddl: true
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        format_sql: true
複製代碼

其他的測試代碼同上一篇文章《Spring Boot (三): ORM 框架 JPA 與鏈接池 Hikari》,有興趣的讀者能夠訪問 Github 倉庫獲取,筆者這裏就不一一列舉了。

4. 測試

咱們在主配置文件中,選擇密碼加密的配置文件啓動,將 spring.profiles.active 配置爲 decrypt ,點擊啓動,能夠看到工程正常啓動,查看控制檯輸出日誌,其中有這麼一句:

2019-09-22 21:21:54.501  INFO 16972 --- [-Log-1465691120] c.a.d.p.DruidDataSourceStatLoggerImpl    : {"url":"jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false","dbType":"mysql","name":"DataSource-1465691120","activeCount":0,"poolingCount":3,"poolingPeak":3,"poolingPeakTime":"2019-09-22 21:21:54","connectCount":0,"closeCount":0,"physicalConnectCount":3}
複製代碼

能夠看到,咱們配置的監控信息輸出會在系統啓動的時候先輸出一次,咱們在配置文件中配置的是每5分鐘輸出一次,等十分鐘看一下控制檯的輸出信息,結果以下:

2019-09-22 21:26:54.503  INFO 16972 --- [-Log-1465691120] c.a.d.p.DruidDataSourceStatLoggerImpl    : {"url":"jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false","dbType":"mysql","name":"DataSource-1465691120","activeCount":0,"activePeak":1,"activePeakTime":"2019-09-22 21:21:54","poolingCount":3,"poolingPeak":3,"poolingPeakTime":"2019-09-22 21:21:54","connectCount":2,"closeCount":2,"connectionHoldTimeHistogram":[0,0,2]}

2019-09-22 21:31:54.505  INFO 16972 --- [-Log-1465691120] c.a.d.p.DruidDataSourceStatLoggerImpl    : {"url":"jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false","dbType":"mysql","name":"DataSource-1465691120","activeCount":0,"poolingCount":3,"connectCount":0,"closeCount":0}

2019-09-22 21:36:54.505  INFO 16972 --- [-Log-1465691120] c.a.d.p.DruidDataSourceStatLoggerImpl    : {"url":"jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false","dbType":"mysql","name":"DataSource-1465691120","activeCount":0,"poolingCount":3,"connectCount":0,"closeCount":0}
複製代碼

從時間上能夠看出,確實是每5分鐘會輸出一次。

打開瀏覽器訪問:http://localhost:8080/druid/ ,查看 Druid 監控頁面,結果如圖:

咱們能夠進行一些接口測試,在查看監控頁面,能夠看到全部的 SQL 都正常記錄,如圖:

同時,咱們看一下後臺的日誌打印,是否正常打出記錄的日誌,截取打印部分,以下:

2019-09-22 21:51:54.506  INFO 16972 --- [-Log-1465691120] c.a.d.p.DruidDataSourceStatLoggerImpl    : {"url":"jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false","dbType":"mysql","name":"DataSource-1465691120","activeCount":0,"activePeak":1,"activePeakTime":"2019-09-22 21:47:28","poolingCount":3,"poolingPeak":3,"poolingPeakTime":"2019-09-22 21:47:28","connectCount":4,"closeCount":4,"executeCount":4,"commitCount":4,"pstmtCacheHitCount":2,"pstmtCacheMissCount":2,"startTransactionCount":4,"transactionHistogram":[0,1,2,1],"connectionHoldTimeHistogram":[0,1,0,3],"sqlList":[{"sql":"insert into user (age, nick_name, id) values (?, ?, ?)","executeCount":2,"executeMillisMax":1,"executeMillisTotal":2,"executeHistogram":[1,1],"executeAndResultHoldHistogram":[1,1],"concurrentMax":1,"updateCount":2,"updateCountMax":1,"updateHistogram":[0,2],"inTransactionCount":2},{"sql":"select usermodel0_.id as id1_0_, usermodel0_.age as age2_0_, usermodel0_.nick_name as nick_nam3_0_ from user usermodel0_ order by usermodel0_.id desc","executeCount":2,"executeMillisMax":3,"executeMillisTotal":4,"executeHistogram":[0,2],"executeAndResultHoldHistogram":[2],"concurrentMax":1,"fetchRowCount":4,"fetchRowCountMax":2,"fetchRowHistogram":[0,2],"inTransactionCount":2}]}
複製代碼

能夠看到,日誌中打印了咱們執行的 SQL 相關的信息,和咱們在監控頁面看到的信息徹底一致。

至此,測試成功,篇幅緣由,一些測試過程未列出,各位感興趣的讀者朋友能夠本身動手嘗試一下。

5. 示例代碼

示例代碼-Github

示例代碼-Gitee

6. 參考

《Druid 官方文檔》

相關文章
相關標籤/搜索