Spring雜談之好用的連接池

日常開發(fā)中,數(shù)據(jù)庫連接池是個必不可少的配置,使用優(yōu)秀的數(shù)據(jù)庫連接池,可以有效的提高數(shù)據(jù)庫訪問效
率,降低連接異常等,本篇就來學(xué)習一下Spirngboot自帶連接池和阿里Druid兩個最常見的連接池

什么是HikariCP

HikariCP是由日本程序員開源的一個數(shù)據(jù)庫連接池組件,代碼非常輕量,并且速度非常的快。根據(jù)官方提供的數(shù)據(jù),在i7,開啟32個線程32個連接的情況下,進行隨機數(shù)據(jù)庫讀寫操作,HikariCP的速度是現(xiàn)在常用的C3P0數(shù)據(jù)庫連接池的數(shù)百倍。在SpringBoot2.0中,官方默認也是使用的HikariCP作為數(shù)據(jù)庫連接池,可見HikariCP連接池的目的就是為了極致的數(shù)據(jù)庫連接性能體驗,下面附上一張HikariCP和其他連接池的比較圖:


HikariCP連接池性能圖.png

從圖中的結(jié)果可以看出來,HikariCP從性能來說的確一騎絕塵,那么HikariCP是如何做到這么極致的性能呢?主要依托于HikariCP自身所做的優(yōu)化機制

HikariCP優(yōu)化機制

字節(jié)碼精簡

HikariCP優(yōu)化了代碼,盡量減少了生成的字節(jié)碼,使得cpu可以加載更多程序代碼

優(yōu)化了攔截和代理機制

HikariCP對攔截器機制和代理機制進行了代碼優(yōu)化處理,例如Statement proxy只有100行代碼,大大減少了代碼量,只有其他連接池例如BoneCP的十分之一

自定義數(shù)組

HikariCP針對數(shù)組操作進行了自定義數(shù)組--FastStatementList,用來替代jdk的ArrayList,優(yōu)化了get、remove等方法,避免了每次調(diào)用get的時候進行范圍檢查,也避免了每次remove操作的時候會將數(shù)據(jù)從頭到尾進行掃描的性能問題

自定義集合

同樣的,針對jdk自帶的集合類,HikariCP專門封裝了無鎖的集合類型 ,用來提高在使用中的讀寫并發(fā)的效率,減少并發(fā)造成的資源競爭問題

CPU時間片算法優(yōu)化

HikariCP也對cpu時間片分配算法進行了優(yōu)化,盡可能使得一個時間片內(nèi)完成相關(guān)的操作

使用HikariCP

了解了HikariCP以后,我們開始使用吧,首先找到HikariCP的坐標:

<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>2.7.6</version>
</dependency>

然后配置HikariCP對應(yīng)的配置文件,用來讀取/加載連接池配置:

/**
 * HikariCP連接池配置
 */
@Configuration
public class DataSourceConfig {

    @Value("${spring.datasource.url}")
    private String dataSourceUrl;

    @Value("${spring.datasource.username}")
    private String user;

    @Value("${spring.datasource.password}")
    private String password;

    @Bean
    public DataSource primaryDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(dataSourceUrl); //數(shù)據(jù)源url
        config.setUsername(user); //用戶名
        config.setPassword(password); //密碼
        config.addDataSourceProperty("cachePrepStmts", "true"); //是否自定義配置,為true時下面兩個參數(shù)才生效
        config.addDataSourceProperty("prepStmtCacheSize", "250"); //連接池大小默認25,官方推薦250-500
        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); //單條語句最大長度默認256,官方推薦2048
        config.addDataSourceProperty("useServerPrepStmts", "true"); //新版本MySQL支持服務(wù)器端準備,開啟能夠得到顯著性能提升
        config.addDataSourceProperty("useLocalSessionState", "true");
        config.addDataSourceProperty("useLocalTransactionState", "true");
        config.addDataSourceProperty("rewriteBatchedStatements", "true");
        config.addDataSourceProperty("cacheResultSetMetadata", "true");
        config.addDataSourceProperty("cacheServerConfiguration", "true");
        config.addDataSourceProperty("elideSetAutoCommits", "true");
        config.addDataSourceProperty("maintainTimeStats", "false");

        HikariDataSource ds = new HikariDataSource(config);
        return ds;
    }
}

接著我們在SpringBoot的application.properties 文件中進行配置:

server.port=8090

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driverClassName=com.mysql.jdbc.Driver

spring.datasource.max-idle=10
spring.datasource.max-active=15
spring.datasource.max-lifetime=86430000
spring.datasource.log-abandoned=true
spring.datasource.remove-abandoned=true
spring.datasource.remove-abandoned-timeout=60
spring.datasource.initialize=false
spring.datasource.sqlScriptEncoding=UTF-8

配置完畢,此時我們啟動工程,即可看到控制臺已經(jīng)將我們配置的HikariCP數(shù)據(jù)庫連接池信息打印出來了

阿里巴巴Druid

提到大名鼎鼎的Druid連接池,相信很多人都不陌生,因為該連接池是阿里開源的優(yōu)秀的連接池,幾乎已經(jīng)成為現(xiàn)在使用最多的連接池之一。我們先打開Druid的官方github:

https://github.com/alibaba/druid 

可以看到此項目已經(jīng)有19.4k的star數(shù),并且是2019最受歡迎的開源之一,經(jīng)歷過真實線上雙十一的考驗,可以說是個很成熟的開源連接池,而Druid連接池專為監(jiān)控而生,內(nèi)置強大的監(jiān)控功能,監(jiān)控特性不影響性能。功能強大,能防SQL注入,內(nèi)置Logging能診斷Hack應(yīng)用行為等。

快速使用

Druid 0.1.18 之后版本都發(fā)布到maven中央倉庫中,所以你只需要在項目的pom.xml中加上dependency就可以了,中央倉庫坐標如下:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>${druid-version}</version>
</dependency>

這里我們使用了springBoot,由于默認支持的數(shù)據(jù)連接池只有四種:dbcp,dbcp2, tomcat, hikariCP,并不包含druid,所以我們這里也可以選擇直接使用阿里官方編寫的druid-spring-boot-starter,并且我們添加對應(yīng)的mybatis和pageHelper的依賴:

<!-- 數(shù)據(jù)庫驅(qū)動 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Mybatis -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.0</version>
</dependency>
<!--Mybatis 分頁插件 pagehelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.1.1</version>
</dependency>
<!-- Druid連接池包 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.1</version>
</dependency>

在application.properties中 進行數(shù)據(jù)源配置:

# 數(shù)據(jù)庫訪問配置
# 主數(shù)據(jù)源,默認的
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123456

# 下面為連接池的補充設(shè)置,應(yīng)用到上面所有數(shù)據(jù)源中
# 初始化大小,最小,最大
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
# 配置獲取連接等待超時的時間
spring.datasource.maxWait=60000
# 配置間隔多久才進行一次檢測,檢測需要關(guān)閉的空閑連接,單位是毫秒 
spring.datasource.timeBetweenEvictionRunsMillis=60000
# 配置一個連接在池中最小生存的時間,單位是毫秒 
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
# 打開PSCache,并且指定每個連接上PSCache的大小 
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
# 配置監(jiān)控統(tǒng)計攔截的filters,去掉后監(jiān)控界面sql無法統(tǒng)計,'wall'用于防火墻 
spring.datasource.filters=stat,wall,log4j
# 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多個DruidDataSource的監(jiān)控數(shù)據(jù)
#spring.datasource.useGlobalDataSourceStat=true

配置完畢以后,由于這里我們使用了mybatis,所以我們還要在application.properties中配置一下mybatis相關(guān):

#mybatis
#entity掃描的包名
mybatis.type-aliases-package=com.xiaolyuh.domain.model
#Mapper.xml所在的位置
mybatis.mapper-locations=classpath*:/mybaits/*Mapper.xml
#開啟MyBatis的二級緩存
mybatis.configuration.cache-enabled=true

#pagehelper
pagehelper.helperDialect=mysql
pagehelper.reasonable=true
pagehelper.supportMethodsArguments=true
pagehelper.params=count=countSql

準備就緒后,我們來編寫一個測試類:

@RunWith(SpringRunner.class)
@SpringBootTest
public class DataSourceTests {

    @Autowired
    ApplicationContext applicationContext;

    @Test
    public void testDataSource() throws Exception {
        // 獲取配置的數(shù)據(jù)源
        DataSource dataSource = applicationContext.getBean(DataSource.class);
System.out.println(dataSource.getClass().getName());
    }
}

將測試方法運行起來,即可在控制臺中看到對應(yīng)的數(shù)據(jù)源的輸出信息

Druid開啟監(jiān)控統(tǒng)計功能

druid最強大的功能就是自身提供了對sql的數(shù)據(jù)監(jiān)控功能,并且內(nèi)置了很多詳細的攔截器,可以實現(xiàn)多個角度的攔截處理,那么如何開啟監(jiān)控?在Druid中內(nèi)置提供了一個StatViewServlet用于展示Druid的統(tǒng)計信息,這個StatViewServlet的用途包括:

  • 提供監(jiān)控信息展示的html頁面
  • 提供監(jiān)控信息的JSON API

如果是ssm工程,則可以在web.xml中配置StatViewServlet,如下:

<servlet>
      <servlet-name>DruidStatView</servlet-name>
      <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
  </servlet>
  <servlet-mapping>
      <servlet-name>DruidStatView</servlet-name>
      <url-pattern>/druid/*</url-pattern>
  </servlet-mapping>

配置完畢以后,啟動工程則可以按照配置的監(jiān)控地址訪問監(jiān)控信息,默認為:http://ip:port/project-name/druid/index.html

同樣的,在StatViewServlet中我們可以添加訪問密碼的設(shè)置,只需要配置Servlet的 loginUsernameloginPassword這兩個初始參數(shù) 即可,例如:

<!-- 配置 Druid 監(jiān)控信息顯示頁面 -->  
<servlet>  
    <servlet-name>DruidStatView</servlet-name>  
    <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>  
    <init-param>  
    <!-- 允許清空統(tǒng)計數(shù)據(jù) -->  
    <param-name>resetEnable</param-name>  
    <param-value>true</param-value>  
    </init-param>  
    <init-param>  
    <!-- 用戶名 -->  
    <param-name>loginUsername</param-name>  
    <param-value>druid</param-value>  
    </init-param>  
    <init-param>  
    <!-- 密碼 -->  
    <param-name>loginPassword</param-name>  
    <param-value>druid</param-value>  
    </init-param>  
</servlet>  
<servlet-mapping>  
    <servlet-name>DruidStatView</servlet-name>  
    <url-pattern>/druid/*</url-pattern>  
</servlet-mapping> 

此時訪問監(jiān)控頁面的時候就需要輸入我們設(shè)置的用戶名和密碼了,如果還想針對用戶有敏感信息配置和訪問權(quán)限控制,我們還可以配置allowdeny參數(shù),例如:

<servlet>
      <servlet-name>DruidStatView</servlet-name>
      <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
    <init-param>
        <param-name>allow</param-name>
        <param-value>128.242.127.1/24,128.242.128.1</param-value>
    </init-param>
    <init-param>
        <param-name>deny</param-name>
        <param-value>128.242.127.4</param-value>
    </init-param>
  </servlet>

這里的訪問規(guī)則為:

1.deny配置優(yōu)先于allow,即deny為優(yōu)先拒絕,即使在allow中配置了白名單,但是只要存在于deny中,一樣也會被拒絕訪問

2.如果allow沒有配置,或者配置為空,默認為全部都可以訪問,不進行白名單限制

使用SpringBoot配置監(jiān)控

由于我們這里使用的是SpringBoot,所以我們僅需要在application.properties 添加配置統(tǒng)計攔截的filters:

# 配置監(jiān)控統(tǒng)計攔截的filters,去掉后監(jiān)控界面sql無法統(tǒng)計,'wall'用于防火墻
spring.datasource.druid.filters=stat,wall,log4j

這里的配置是通過別名方式配置擴展支持的插件,如下:

  • 監(jiān)控統(tǒng)計用的filter:stat
  • 日志用的filter:log4j
  • 防御sql注入的filter:wall

接著我們需要在application.properties繼續(xù)添加WebStatFilterStatViewServlet的配置項:

# WebStatFilter配置,說明請參考Druid Wiki,配置_配置WebStatFilter
#啟動項目后訪問 http://127.0.0.1:8080/druid
#是否啟用StatFilter默認值true
spring.datasource.druid.web-stat-filter.enabled=true
spring.datasource.druid.web-stat-filter.url-pattern=/*
spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*
#缺省sessionStatMaxCount是1000個
spring.datasource.druid.web-stat-filter.session-stat-max-count=1000
#關(guān)閉session統(tǒng)計功能
spring.datasource.druid.web-stat-filter.session-stat-enable=false
#配置principalSessionName,使得druid能夠知道當前的session的用戶是誰
#如果你session中保存的是非string類型的對象,需要重載toString方法
spring.datasource.druid.web-stat-filter.principalSessionName=xxx.user
#如果user信息保存在cookie中,你可以配置principalCookieName,使得druid知道當前的user是誰
spring.datasource.druid.web-stat-filter.principalCookieName=xxx.user
#druid 0.2.7版本開始支持profile,配置profileEnable能夠監(jiān)控單個url調(diào)用的sql列表。
spring.datasource.druid.web-stat-filter.profile-enable=false

# StatViewServlet配置,說明請參考Druid Wiki,配置_StatViewServlet配置
#啟動項目后訪問 http://127.0.0.1:8080/druid
#是否啟用StatViewServlet默認值true
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.urlPattern=/druid/*
#禁用HTML頁面上的“Reset All”功能
spring.datasource.druid.stat-view-servlet.resetEnable=false
#用戶名
spring.datasource.druid.stat-view-servlet.loginUsername=admin
#密碼
spring.datasource.druid.stat-view-servlet.loginPassword=admin
#IP白名單(沒有配置或者為空,則允許所有訪問)
spring.datasource.druid.stat-view-servlet.allow=127.0.0.1,192.168.163.1
#IP黑名單 (存在共同時,deny優(yōu)先于allow)
spring.datasource.druid.stat-view-servlet.deny=192.168.1.73

接著我們啟動工程,訪問http://localhost/druid ,輸入配置的用戶名:admin以及密碼:admin,即可看到druid的監(jiān)控頁面:

druid監(jiān)控頁面.png

慢sql日志打印

在開發(fā)過程中,往往會遇到sql時間過長問題,為了定位慢sql,我們往往會定義固定時長作為慢sql的時長,而Druid支持慢sql查詢,在Druid中內(nèi)置提供了一個StatFilter,用于統(tǒng)計監(jiān)控信息 ,我們可以利用這個StatFilter來統(tǒng)計慢sql:

StatFilter的別名是stat,這個別名映射配置信息保存在druid-xxx.jar!/META-INF/druid-filter.properties

我們需要在Spring中加入以下配置:

 <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="filters" value="stat" />
  </bean>

當然如果需要我們可以同時開啟多個Filter進行組合使用,中間用,隔開即可,如下:

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="filters" value="stat,log4j" />
  </bean>

而如果開啟慢sql的記錄,我們需要先定義slowSqlMillis 來配置sql慢查詢的標準,如下:

<bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter">
    <property name="slowSqlMillis" value="10000" />
    <property name="logSlowSql" value="true" />
</bean>

配置完畢以后,所有的超過10s的sql都會在監(jiān)控頁面的慢sql模塊記錄,可以查看具體的sql以及執(zhí)行時間等,快速定位開發(fā)過程中的慢sql

DruidDataSource配置

如果我們根據(jù)業(yè)務(wù)的不同,需要更改不同的配置,這個時候我們就需要參考DriudDataSource的配置,通用的配置項如下:

配置 缺省值 描述
name 如果存在多個數(shù)據(jù)源,監(jiān)控的時候可以通過名字來區(qū)分開來。如果沒有配置,將會生成一個名字,格式是:"DataSource-" + System.identityHashCode(this)
url 連接數(shù)據(jù)庫的url
username 連接數(shù)據(jù)庫的用戶名
password 連接數(shù)據(jù)庫的密碼
driverClassName 根據(jù)url自動識別 可配可不配,如果不配置druid會根據(jù)url自動識別dbType,然后選擇相應(yīng)的driverClassName
initialSize 0 初始化時建立物理連接的個數(shù)。初始化發(fā)生在顯示調(diào)用init方法,或者第一次getConnection時
maxActive 8 最大連接池數(shù)量
maxIdle 8 已經(jīng)棄用
minIdle 最小連接池數(shù)量
maxWait 獲取連接時最大等待時間,單位毫秒。配置了maxWait之后,缺省啟用公平鎖,并發(fā)效率會有所下降,如果需要可以通過配置useUnfairLock屬性為true使用非公平鎖
poolPreparedStatements false 是否緩存preparedStatement,也就是PSCache。PSCache對支持游標的數(shù)據(jù)庫性能提升巨大,比如說oracle。在mysql下建議關(guān)閉。
maxPoolPreparedStatementPerConnectionSize -1 要啟用PSCache,必須配置大于0,當大于0時,poolPreparedStatements自動觸發(fā)修改為true。在Druid中,不會存在Oracle下PSCache占用內(nèi)存過多的問題,可以把這個數(shù)值配置大一些,比如說100
validationQuery 用來檢測連接是否有效的sql,要求是一個查詢語句,常用select 'x'。如果validationQuery為null,testOnBorrow、testOnReturn、testWhileIdle都不會起作用。
validationQueryTimeout 單位:秒,檢測連接是否有效的超時時間。
testOnBorrow true 申請連接時執(zhí)行validationQuery檢測連接是否有效,做了這個配置會降低性能。
testOnReturn false 歸還連接時執(zhí)行validationQuery檢測連接是否有效,做了這個配置會降低性能。
testWhileIdle false 建議配置為true,不影響性能,并且保證安全性。申請連接的時候檢測,如果空閑時間大于timeBetweenEvictionRunsMillis,執(zhí)行validationQuery檢測連接是否有效
keepAlive false 連接池中的minIdle數(shù)量以內(nèi)的連接,空閑時間超過minEvictableIdleTimeMillis,則會執(zhí)行keepAlive操作
timeBetweenEvictionRunsMillis 60s 有兩個含義: 1) Destroy線程會檢測連接的間隔時間,如果連接空閑時間大于等于minEvictableIdleTimeMillis則關(guān)閉物理連接。 2) testWhileIdle的判斷依據(jù),詳細看testWhileIdle屬性的說明
numTestsPerEvictionRun 30分鐘 已經(jīng)棄用
minEvictableIdleTimeMillis 連接保持空閑而不被驅(qū)逐的最小時間
connectionInitSqls 物理連接初始化的時候執(zhí)行的sql
exceptionSorter 根據(jù)dbType自動識別 當數(shù)據(jù)庫拋出一些不可恢復(fù)的異常時,拋棄連接
filters 屬性類型是字符串,通過別名的方式配置擴展插件,常用的插件有: 監(jiān)控統(tǒng)計用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall
proxyFilters 類型是List<com.alibaba.druid.filter.Filter>,如果同時配置了filters和proxyFilters,是組合關(guān)系,并非替換關(guān)系
Druid常見配置與問題

除了我們已經(jīng)了解的druid常見知識以外,開發(fā)中經(jīng)常還會遇到很多其他常見需求,如開啟druid的防sql注入功能、記錄每次執(zhí)行的sql、數(shù)據(jù)庫加密、log輸出執(zhí)行的sql等常見需求,這個時候我們就可以在官方的github的文檔中查找,官方已經(jīng)給我們整理好了一些開發(fā)常見的問題,地址如下:

https://github.com/alibaba/druid/wiki/常見問題

總結(jié)

在實際開發(fā)過程中,我們往往會根據(jù)自身需求或者項目本身來選擇最適合的連接池,這里我們將常見的數(shù)據(jù)連接池從可擴展性、可靠穩(wěn)定性、性能、可運維性以及自身功能幾個方向進行了比較,可供參考:


連接池比較圖.jpg
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容