在大型業(yè)務(wù)系統(tǒng)上線后,為了保證系統(tǒng)能夠更好地持續(xù)穩(wěn)定運(yùn)行,及時(shí)發(fā)現(xiàn)各種故障(代碼缺陷、SQL性能問題、服務(wù)器CPU/磁盤參數(shù)指標(biāo)和各類業(yè)務(wù)異常等),因此需要針對(duì)系統(tǒng)開發(fā)各種監(jiān)控功能。在微服務(wù)架構(gòu)下的各類業(yè)務(wù)平臺(tái)中,針對(duì)SQL進(jìn)行監(jiān)控,并根據(jù)業(yè)務(wù)的發(fā)展情況及時(shí)進(jìn)行調(diào)優(yōu)尤為重要。如果讓中間件或者業(yè)務(wù)研發(fā)團(tuán)隊(duì)自己根據(jù)業(yè)務(wù)特征定制化開發(fā)一套SQL的監(jiān)控系統(tǒng),可能既費(fèi)時(shí)費(fèi)力,又不一定能夠達(dá)到預(yù)定的結(jié)果。本文將介紹業(yè)界較為流行的Druid數(shù)據(jù)源連接池插件,并跟其他幾款熱門的數(shù)據(jù)源連接池進(jìn)行對(duì)比分析,最后給出在Spring Boot工程中集成該數(shù)據(jù)源連接池的實(shí)踐方法。
1. Druid數(shù)據(jù)庫連接池介紹
Druid數(shù)據(jù)源連接池來源于阿里巴巴,是淘寶和支付寶專用數(shù)據(jù)庫連接池。事實(shí)上,它不僅僅是一個(gè)數(shù)據(jù)庫連接池,還包含一個(gè)ProxyDriver、一系列內(nèi)置的JDBC組件庫、一個(gè) SQL Parser。支持所有JDBC兼容的數(shù)據(jù)庫,包括Oracle、MySql、Derby、Postgresql、SQL Server、H2等等。Druid針對(duì)Oracle和MySql做了特別優(yōu)化,比如Oracle的PSCache內(nèi)存占用優(yōu)化,MySql的ping檢測(cè)優(yōu)化。Druid提供了諸如MySql、Oracle、Postgresql、SQL-92等SQL語句的完美支持,是一個(gè)手寫的高性能SQL Parser,支持Visitor模式,使得分析SQL的抽象語法樹很方便。它執(zhí)行簡單SQL語句耗時(shí)在10微秒以內(nèi),對(duì)于復(fù)雜的SQL語句耗時(shí)也在30微秒左右。另外,通過Druid提供的SQL Parser可以在JDBC層面上攔截SQL并進(jìn)行相應(yīng)處理,比如說分庫分表、SQL安全審計(jì)等。Druid也能防御SQL注入攻擊,WallFilter就是通過Druid的SQL Parser分析語義實(shí)現(xiàn)的。
2. 與其他幾種數(shù)據(jù)庫連接池進(jìn)行對(duì)比
通過下面的表格先來看下Druid與當(dāng)前比較流行的其他幾款數(shù)據(jù)源連接池的對(duì)比:
| 功能 | dbcp | druid | c3p0 | tomcat-jdbc | HikariCP |
|---|---|---|---|---|---|
| 支持PSCache | 是 | 是 | 是 | 否 | 否 |
| 監(jiān)控 | jmx | jmx/log/http | jmx,log | jmx | jmx |
| 擴(kuò)展 | 弱 | 好 | 弱 | 弱 | 弱 |
| sql攔截及解析 | 無 | 支持 | 無 | 無 | 無 |
| 代碼 | 簡單 | 中等 | 復(fù)雜 | 簡單 | 簡單 |
| 特點(diǎn) | 依賴common-pool | 阿里開源,功能全面 | 代碼邏輯復(fù)雜,且不易維護(hù) | 功能簡單,起源于boneCP | |
| 連接池管理 | LinkedBlockingDeque | 數(shù)組 | 更新 | FairBlockingQueue | threadlocal+CopyOnWriteArrayList |
從上面對(duì)比的表格中,可以看到druid功能最為全面,具備sql攔截等功能,其中統(tǒng)計(jì)數(shù)據(jù)較為全面,具有良好的擴(kuò)展性。雖然在性能方面比HikariCP略差,但是綜合其他方面來考慮在做技術(shù)選型的時(shí)候,可以選擇Druid作為數(shù)據(jù)源連接池組件來用。
3. 動(dòng)手在Spring-Boot工程中添加Druid實(shí)踐
本文前面兩節(jié)都是主要講了理論,相對(duì)比較枯燥。下面這一節(jié)將從實(shí)踐的角度,來一步一步向大家展示如何在Spring Boot工程中添加Druid連接池進(jìn)行業(yè)務(wù)級(jí)的SQL監(jiān)控。
版本環(huán)境
Spring Boot 1.4.1.RELEASE、Druid 1.0.12、JDK 1.8
在工程中添加Druid的pom依賴
因?yàn)榘⒗镩_源了Druid的數(shù)據(jù)源連接池源碼,我們可以通過maven倉庫可以獲得jar包依賴。訪問http://mvnrepository.com/artifact/com.alibaba/druid選擇自己項(xiàng)目需要的版本(在本次集成中選擇的是1.0.12),點(diǎn)擊進(jìn)入后復(fù)制maven內(nèi)容到pom.xml內(nèi)即可,如下所示:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.12</version>
</dependency>
在自己工程中添加完以上Druid數(shù)據(jù)源連接池的依賴后,記得在Intellij中點(diǎn)擊下"Enable Auto import"選項(xiàng)即可自動(dòng)下載maven依賴的jar到本地.m2目錄并構(gòu)建到項(xiàng)目中。添加Druid至Spring Boot工程中就這么Easy,這么快捷。
在Spring Boot工程中添加Druid配置
在上面我們已經(jīng)將Druid添加至項(xiàng)目中,接下來需要修改Spring Boot的application.yml配置文件,來添加Druid數(shù)據(jù)源連接池的支持,如下所示:
server.port=8888
# 數(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?useUnicode=true&characterEncoding=gbk&zeroDateTimeBehavior=convertToNull&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
# 下面為連接池的補(bǔ)充設(shè)置,應(yīng)用到上面所有數(shù)據(jù)源中
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
# 配置獲取連接等待超時(shí)的時(shí)間
spring.datasource.maxWait=60000
# 配置間隔多久才進(jìn)行一次檢測(cè),檢測(cè)需要關(guān)閉的空閑連接,單位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
# 配置一個(gè)連接在池中最小生存的時(shí)間,單位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
# 配置監(jiān)控統(tǒng)計(jì)攔截的filters,去掉后監(jiān)控界面sql無法統(tǒng)計(jì),'wall'用于防火墻
spring.datasource.filters=stat,wall,log4j
spring.datasource.logSlowSql=true
需要說明的是,上面配置中的filters:stat表示已經(jīng)可以使用監(jiān)控過濾器,這時(shí)結(jié)合定義一個(gè)過濾器,我們就可以用其來監(jiān)控SQL的執(zhí)行情況。
開啟Druid的SQL監(jiān)控功能
在工程中開啟監(jiān)控功能后,可以在工程應(yīng)用運(yùn)行過程中,通過Druid數(shù)據(jù)源連接池自帶SQL監(jiān)控提供的多維度數(shù)據(jù),分析出業(yè)務(wù)SQL執(zhí)行的情況,從而可以調(diào)整和優(yōu)化代碼以及SQL,方便業(yè)務(wù)開發(fā)同事調(diào)優(yōu)數(shù)據(jù)庫的訪問性能。
要達(dá)到開啟SQL監(jiān)控的效果,還需在Spring Boot工程中還實(shí)現(xiàn)Druid數(shù)據(jù)源連接池的Serlvet以及Filter,其Bean的初始化代碼如下(下面給出兩種配置方式):
第一種方式@Confing注解的配置類:
/**
* druid 配置.
* <p>
* 這樣的方式不需要添加注解:@ServletComponentScan
*
* @author Administrator
*/
@Configuration
public class DruidConfiguration {
/**
* 注冊(cè)一個(gè)StatViewServlet
*
* @return
*/
@Bean
public ServletRegistrationBean DruidStatViewServle2() {
//org.springframework.boot.context.embedded.ServletRegistrationBean提供類的進(jìn)行注冊(cè).
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
servletRegistrationBean.addUrlMappings("/druid/*");
//添加初始化參數(shù):initParams
//白名單:
servletRegistrationBean.addInitParameter("allow", "127.0.0.1");
//IP黑名單 (存在共同時(shí),deny優(yōu)先于allow) : 如果滿足deny的話提示:Sorry, you are not permitted to view this page.
servletRegistrationBean.addInitParameter("deny", "192.168.1.73");
//登錄查看信息的賬號(hào)密碼.
servletRegistrationBean.addInitParameter("loginUsername", "admin");
servletRegistrationBean.addInitParameter("loginPassword", "admin");
//是否能夠重置數(shù)據(jù).
servletRegistrationBean.addInitParameter("resetEnable", "false");
return servletRegistrationBean;
}
/**
* 注冊(cè)一個(gè):filterRegistrationBean
*
* @return
*/
@Bean
public FilterRegistrationBean druidStatFilter2() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
//添加過濾規(guī)則.
filterRegistrationBean.addUrlPatterns("/*");
//添加不需要忽略的格式信息.
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid2/*");
return filterRegistrationBean;
}
}
第二種方式基于注解的配置:
/**
* druid數(shù)據(jù)源狀態(tài)監(jiān)控.
*
* @author Administrator
*/
@WebServlet(urlPatterns = "/druid/*",
initParams = {
@WebInitParam(name = "allow", value = "192.168.1.72,127.0.0.1"),// IP白名單(沒有配置或者為空,則允許所有訪問)
@WebInitParam(name = "deny", value = "192.168.1.73"),// IP黑名單 (存在共同時(shí),deny優(yōu)先于allow)
@WebInitParam(name = "loginUsername", value = "admin"),// 用戶名
@WebInitParam(name = "loginPassword", value = "admin"),// 密碼
@WebInitParam(name = "resetEnable", value = "false")// 禁用HTML頁面上的“Reset All”功能
}
)
public class DruidStatViewServlet extends StatViewServlet {
private static final long serialVersionUID = 1L;
}
/**
* druid過濾器.
*
* @author Administrator
*/
@WebFilter(filterName = "druidWebStatFilter", urlPatterns = "/*",
initParams = {
@WebInitParam(name = "exclusions", value = "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*")//忽略資源
}
)
public class DruidStatFilter extends WebStatFilter {
}
使用上面第二種方式的話,還需要在Spring Boot工程的啟動(dòng)類上添加注解:@ServletComponentScan,這樣使Spring能夠掃描到我們自己編寫的servlet和filter。
使用Druid進(jìn)行SQL監(jiān)控的效果
我們已經(jīng)配置完成了Druid的監(jiān)控,在本地運(yùn)行Spring Boot的Jar包,運(yùn)行成功后即可訪問Druid監(jiān)控界面,默認(rèn)訪問地址為:http://localhost:8080/druid/,最終的效果圖如下所示:

可以看到了我們成功的訪問了Druid的監(jiān)控頁面,那么現(xiàn)在輸入我們?cè)贐ean初始化時(shí)候設(shè)置的用戶名、密碼(admin/admin)登錄監(jiān)控平臺(tái),進(jìn)入監(jiān)控平臺(tái)首頁,如下所示:


有了Web UI我們就可以方便的從這個(gè)UI上看到該工程部署起來后數(shù)據(jù)源初始化配置以及業(yè)務(wù)級(jí)SQL的執(zhí)行情況。
4. 總結(jié)
本文圍繞Druid數(shù)據(jù)源連接池為主題,先簡要地介紹了該連接池的功能,然后通過與業(yè)界幾款較為流行的數(shù)據(jù)源連接池進(jìn)行橫向?qū)Ρ?,分析出Druid連接池的特色和優(yōu)勢(shì)。最后通過實(shí)踐,進(jìn)一步向大家闡述如何在一個(gè)Spring Boot工程中添加Druid連接池進(jìn)行業(yè)務(wù)SQL級(jí)別的監(jiān)控。