Druid連接池(二)
六、Druid關聯
6.1、Web關聯監控配置
WebStatFilter用於采集web-jdbc關聯監控的數據。
web.xml配置:
<filter>
<filter-name>DruidWebStatFilter</filter-name>
<filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
<init-param>
<param-name>exclusions</param-name>
<param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>DruidWebStatFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
exlusions配置:
經常需要排除一些不必要的url,比如.js,/jslib/等等。配置在init-param中。比如:
<init-param>
<param-name>exclusions</param-name>
<param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
</init-param>
sessionStatMaxCount配置:
缺省sessionStatMaxCount是1000個。你可以按需要進行配置,比如:
<init-param>
<param-name>sessionStatMaxCount</param-name>
<param-value>1000</param-value>
</init-param>
sessionStatEnable配置:
你可以關閉session統計功能,比如:
<init-param>
<param-name>sessionStatEnable</param-name>
<param-value>false</param-value>
</init-param>
principalSessionName配置:
你可以配置principalSessionName,使得druid能夠知道當前的session的用戶是誰。比如:
<init-param>
<param-name>principalSessionName</param-name>
<param-value>xxx.user</param-value>
</init-param>
根據需要,把其中的xxx.user修改為你user信息保存在session中的sessionName。
註意:如果你session中保存的是非string類型的對象,需要重載toString方法。
principalCookieName:
如果你的user信息保存在cookie中,你可以配置principalCookieName,使得druid知道當前的user是誰
<init-param>
<param-name>principalCookieName</param-name>
<param-value>xxx.user</param-value>
</init-param>
根據需要,把其中的xxx.user修改為你user信息保存在cookie中的cookieName
profileEnable:
druid 0.2.7版本開始支持profile,配置profileEnable能夠監控單個url調用的sql列表。
<init-param>
<param-name>profileEnable</param-name>
<param-value>true</param-value>
</init-param>
6.2.Spring關聯監控配置
Druid提供了Spring和Jdbc的關聯監控。
配置spring
com.alibaba.druid.support.spring.stat.DruidStatInterceptor是一個標準的Spring MethodInterceptor。
靈活進行AOP配置。
Spring AOP的配置文檔:
http://static.springsource.org/spring/docs/current/spring-framework-reference/html/aop-api.html
按類型攔截配置:
<bean id="druid-stat-interceptor" class="com.alibaba.druid.support.spring.stat.DruidStatInterceptor">
</bean>
<bean id="druid-type-proxyCreator" class="com.alibaba.druid.support.spring.stat.BeanTypeAutoProxyCreator">
<!-- 所有ABCInterface的派生類被攔截監控 -->
<property name="targetBeanType" value="xxxx.ABCInterface" />
<property name="interceptorNames">
<list>
<value>druid-stat-interceptor</value>
</list>
</property>
</bean>
方法名正則匹配攔截配置:
<bean id="druid-stat-interceptor" class="com.alibaba.druid.support.spring.stat.DruidStatInterceptor">
</bean>
<bean id="druid-stat-pointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut" scope="prototype">
<property name="patterns">
<list>
<value>com.mycompany.service.*</value>
<value>com.mycompany.dao.*</value>
</list>
</property>
</bean>
<aop:config>
<aop:advisor advice-ref="druid-stat-interceptor" pointcut-ref="druid-stat-pointcut" />
</aop:config>
有些情況下,可能你需要配置proxy-target-class,例如:
<aop:config proxy-target-class="true">
<aop:advisor advice-ref="druid-stat-interceptor" pointcut-ref="druid-stat-pointcut" />
</aop:config>
按照BeanId來攔截配置:
<bean id="druid-stat-interceptor" class="com.alibaba.druid.support.spring.stat.DruidStatInterceptor">
</bean>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="proxyTargetClass" value="true" />
<property name="beanNames">
<list>
<!-- 這裏配置需要攔截的bean id列表 -->
<value>xxx-dao</value>
<value>xxx-service</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>druid-stat-interceptor</value>
</list>
</property>
</bean>
七、Druid防禦
Druid提供了WallFilter,它是基於SQL語義分析來實現防禦SQL註入攻擊的。
這個文檔提供基於Spring的各種配置方式。
使用缺省配置的WallFilter:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
...
<property name="filters" value="wall"/>
</bean>
結合其他Filter一起使用:
WallFilter可以結合其他Filter一起使用,例如:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
...
<property name="filters" value="wall,stat"/>
</bean>
這樣,攔截檢測的時間不在StatFilter統計的SQL執行時間內。
如果希望StatFilter統計的SQL執行時間內,則使用如下配置:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
...
<property name="filters" value="stat,wall"/>
</bean>
指定dbType
有時候,一些應用框架做了自己的JDBC Proxy Driver,是的DruidDataSource無法正確識別數據庫的類型,則需要特別指定,如下:
<bean id="wall-filter" class="com.alibaba.druid.wall.WallFilter">
<property name="dbType" value="mysql" />
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
...
<property name="proxyFilters">
<list>
<ref bean="wall-filter"/>
</list>
</property>
</bean>
指定配置裝載的目錄
缺省情況下,配置裝載的目錄如下:
數據庫類型 |
目錄 |
mysql |
META-INF/druid/wall/mysql |
oracle |
META-INF/druid/wall/oracle |
sqlserver |
META-INF/druid/wall/sqlserver |
postgres |
META-INF/druid/wall/postgres |
從配置目錄中以下文件中讀取配置:
deny-variant.txt deny-schema.txt deny-function.txt deny-table.txt deny-object.txt
指定配置裝載的目錄是可以指定,例如:
<bean id="wall-filter-config" class="com.alibaba.druid.wall.WallConfig" init-method="init">
<!-- 指定配置裝載的目錄 -->
<property name="dir" value="META-INF/druid/wall/mysql" />
</bean>
<bean id="wall-filter" class="com.alibaba.druid.wall.WallFilter">
<property name="dbType" value="mysql" />
<property name="config" ref="wall-filter-config" />
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
...
<property name="proxyFilters">
<list>
<ref bean="wall-filter"/>
</list>
</property>
</bean>
WallConfig詳細說明
本身的配置
配置項 |
缺省值 |
dir |
按照dbType分別配置: |
攔截配置-語句
配置項 |
缺省值 |
描述 |
selelctAllow |
true |
是否允許執行SELECT語句 |
selectAllColumnAllow |
true |
是否允許執行SELECT * FROM T這樣的語句。如果設置為false,不允許執行select * from t,但select * from (select id, name from t) a。這個選項是防禦程序通過調用select *獲得數據表的結構信息。 |
selectIntoAllow |
true |
SELECT查詢中是否允許INTO字句 |
deleteAllow |
true |
是否允許執行DELETE語句 |
updateAllow |
true |
是否允許執行UPDATE語句 |
insertAllow |
true |
是否允許執行INSERT語句 |
replaceAllow |
true |
是否允許執行REPLACE語句 |
mergeAllow |
true |
是否允許執行MERGE語句,這個只在Oracle中有用 |
callAllow |
true |
是否允許通過jdbc的call語法調用存儲過程 |
setAllow |
true |
是否允許使用SET語法 |
truncateAllow |
true |
truncate語句是危險,缺省打開,若需要自行關閉 |
createTableAllow |
true |
是否允許創建表 |
alterTableAllow |
true |
是否允許執行Alter Table語句 |
dropTableAllow |
true |
是否允許修改表 |
commentAllow |
false |
是否允許語句中存在註釋,Oracle的用戶不用擔心,Wall能夠識別hints和註釋的區別 |
noneBaseStatementAllow |
false |
是否允許非以上基本語句的其他語句,缺省關閉,通過這個選項就能夠屏蔽DDL。 |
multiStatementAllow |
false |
是否允許一次執行多條語句,缺省關閉 |
useAllow |
true |
是否允許執行mysql的use語句,缺省打開 |
describeAllow |
true |
是否允許執行mysql的describe語句,缺省打開 |
showAllow |
true |
是否允許執行mysql的show語句,缺省打開 |
commitAllow |
true |
是否允許執行commit操作 |
rollbackAllow |
true |
是否允許執行roll back操作 |
如果把selectIntoAllow、deleteAllow、updateAllow、insertAllow、mergeAllow都設置為false,這就是一個只讀數據源了。
攔截配置-永真條件
配置項 |
缺省值 |
描述 |
selectWhereAlwayTrueCheck |
true |
檢查SELECT語句的WHERE子句是否是一個永真條件 |
selectHavingAlwayTrueCheck |
true |
檢查SELECT語句的HAVING子句是否是一個永真條件 |
deleteWhereAlwayTrueCheck |
true |
檢查DELETE語句的WHERE子句是否是一個永真條件 |
deleteWhereNoneCheck |
false |
檢查DELETE語句是否無where條件,這是有風險的,但不是SQL註入類型的風險 |
updateWhereAlayTrueCheck |
true |
檢查UPDATE語句的WHERE子句是否是一個永真條件 |
updateWhereNoneCheck |
false |
檢查UPDATE語句是否無where條件,這是有風險的,但不是SQL註入類型的風險 |
conditionAndAlwayTrueAllow |
false |
檢查查詢條件(WHERE/HAVING子句)中是否包含AND永真條件 |
conditionAndAlwayFalseAllow |
false |
檢查查詢條件(WHERE/HAVING子句)中是否包含AND永假條件 |
conditionLikeTrueAllow |
true |
檢查查詢條件(WHERE/HAVING子句)中是否包含LIKE永真條件 |
其他攔截配置
配置項 |
缺省值 |
描述 |
selectIntoOutfileAllow |
false |
SELECT ... INTO OUTFILE 是否允許,這個是mysql註入攻擊的常見手段,缺省是禁止的 |
selectUnionCheck |
true |
檢測SELECT UNION |
selectMinusCheck |
true |
檢測SELECT MINUS |
selectExceptCheck |
true |
檢測SELECT EXCEPT |
selectIntersectCheck |
true |
檢測SELECT INTERSECT |
mustParameterized |
false |
是否必須參數化,如果為True,則不允許類似WHERE ID = 1這種不參數化的SQL |
strictSyntaxCheck |
true |
是否進行嚴格的語法檢測,Druid SQL Parser在某些場景不能覆蓋所有的SQL語法,出現解析SQL出錯,可以臨時把這個選項設置為false,同時把SQL反饋給Druid的開發者。 |
conditionOpXorAllow |
false |
查詢條件中是否允許有XOR條件。XOR不常用,很難判斷永真或者永假,缺省不允許。 |
conditionOpBitwseAllow |
true |
查詢條件中是否允許有"&"、"~"、"|"、"^"運算符。 |
conditionDoubleConstAllow |
false |
查詢條件中是否允許連續兩個常量運算表達式 |
minusAllow |
true |
是否允許SELECT * FROM A MINUS SELECT * FROM B這樣的語句 |
intersectAllow |
true |
是否允許SELECT * FROM A INTERSECT SELECT * FROM B這樣的語句 |
constArithmeticAllow |
true |
攔截常量運算的條件,比如說WHERE FID = 3 - 1,其中"3 - 1"是常量運算表達式。 |
limitZeroAllow |
false |
是否允許limit 0這樣的語句 |
禁用對象檢測配置
配置項 |
缺省值 |
描述 |
tableCheck |
true |
檢測是否使用了禁用的表 |
schemaCheck |
true |
檢測是否使用了禁用的Schema |
functionCheck |
true |
檢測是否使用了禁用的函數 |
objectCheck |
true |
檢測是否使用了“禁用對對象” |
variantCheck |
true |
檢測是否使用了“禁用的變量” |
readOnlyTables |
空 |
指定的表只讀,不能夠在SELECT INTO、DELETE、UPDATE、INSERT、MERGE中作為"被修改表"出現 |
Jdbc相關配置
配置項 |
缺省值 |
描述 |
metadataAllow |
true |
是否允許調用Connection.getMetadata方法,這個方法調用會暴露數據庫的表信息 |
wrapAllow |
true |
是否允許調用Connection/Statement/ResultSet的isWrapFor和unwrap方法,這兩個方法調用,使得有辦法拿到原生驅動的對象,繞過WallFilter的檢測直接執行SQL。 |
WallFiler配置說明
配置項 |
缺省值 |
描述 |
logViolation |
false |
對被認為是攻擊的SQL進行LOG.error輸出 |
throwException |
true |
對被認為是攻擊的SQL拋出SQLExcepton |
config |
|
|
provider |
|
|
剛開始引入WallFilter的時候,把logViolation設置為true,而throwException設置為false。就可以觀察是否存在違規的情況,同時不影響業務運行。
八、Druid參考
不同的業務場景需求不同,你可以使用我們的參考配置,但建議你仔細閱讀相關文檔,了解清楚之後做定制配置。
以下是一個參考的連接池配置:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本屬性 url、user、password -->
<property name="url" value="${jdbc_url}" />
<property name="username" value="${jdbc_user}" />
<property name="password" value="${jdbc_password}" />
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1" />
<property name="minIdle" value="1" />
<property name="maxActive" value="20" />
<!-- 配置獲取連接等待超時的時間 -->
<property name="maxWait" value="60000" />
<!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一個連接在池中最小生存的時間,單位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="SELECT ‘x‘" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<!-- 打開PSCache,並且指定每個連接上PSCache的大小 -->
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
<!-- 配置監控統計攔截的filters -->
<property name="filters" value="stat" />
</bean>
通常來說,只需要修改initialSize、minIdle、maxActive。
如果用Oracle,則把poolPreparedStatements配置為true,mysql可以配置為false。分庫分表較多的數據庫,建議配置為false。
十、Druid日誌
Druid提供了Log4jFilter、CommonsLogFilter和Slf4jFilter,具體配置看這裏:
Druid內置提供了三種LogFilter(Log4jFilter、CommonsLogFilter、Slf4jLogFilter),用於輸出JDBC執行的日誌。這些Filter都是Filter-Chain擴展機制中的Filter,所以配置方式可以參考這裏:Filter配置
10.1、別名映射
在druid-xxx.jar!/META-INF/druid-filter.properties文件中描述了這三種Filter的別名
druid.filters.log4j=com.alibaba.druid.filter.logging.Log4jFilter
druid.filters.slf4j=com.alibaba.druid.filter.logging.Slf4jLogFilter
druid.filters.commonlogging=com.alibaba.druid.filter.logging.CommonsLogFilter
druid.filters.commonLogging=com.alibaba.druid.filter.logging.CommonsLogFilter
他們的別名分別是log4j、slf4j、commonlogging和commonLogging。其中commonlogging和commonLogging只是大小寫不同。
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
... ...
<property name="filters" value="stat,log4j" />
</bean>
10.2、loggerName映射
LogFilter都是缺省使用四種不同的Logger執行輸出,看實現代碼:
public abstract class LogFilter {
protected String dataSourceLoggerName = "druid.sql.DataSource";
protected String connectionLoggerName = "druid.sql.Connection";
protected String statementLoggerName = "druid.sql.Statement";
protected String resultSetLoggerName = "druid.sql.ResultSet";
}
你可以根據你的需要修改,在log4j.properties文件上做配置時,註意配置使用相關的logger。
10.3、配置輸出日誌
缺省輸入的日誌信息全面,但是內容比較多,有時候我們需要定制化配置日誌輸出。
<bean id="log-filter" class="com.alibaba.druid.filter.logging.Log4jFilter">
<property name="resultSetLogEnabled" value="false" />
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
...
<property name="proxyFilters">
<list>
<ref bean="log-filter"/>
</list>
</property>
</bean>
參數 |
說明 |
dataSourceLogEnabled |
所有DataSource相關的日誌 |
connectionLogEnabled |
所有連接相關的日誌 |
connectionLogErrorEnabled |
所有連接上發生異常的日誌 |
statementLogEnabled |
所有Statement相關的日誌 |
statementLogErrorEnabled |
所有Statement發生異常的日誌 |
resultSetLogEnabled |
|
resultSetLogErrorEnabled |
|
connectionConnectBeforeLogEnabled |
|
connectionConnectAfterLogEnabled |
|
connectionCommitAfterLogEnabled |
|
connectionRollbackAfterLogEnabled |
|
connectionCloseAfterLogEnabled |
|
statementCreateAfterLogEnabled |
|
statementPrepareAfterLogEnabled |
|
statementPrepareCallAfterLogEnabled |
|
statementExecuteAfterLogEnabled |
|
statementExecuteQueryAfterLogEnabled |
|
statementExecuteUpdateAfterLogEnabled |
|
statementExecuteBatchAfterLogEnabled |
|
statementCloseAfterLogEnabled |
|
statementParameterSetLogEnabled |
|
resultSetNextAfterLogEnabled |
|
resultSetOpenAfterLogEnabled |
|
resultSetCloseAfterLogEnabled |
|
10.4、log4j.properties配置
如果你使用log4j,可以通過log4j.properties文件配置日誌輸出選項,例如:
log4j.logger.druid.sql=warn,stdout
log4j.logger.druid.sql.DataSource=warn,stdout
log4j.logger.druid.sql.Connection=warn,stdout
log4j.logger.druid.sql.Statement=warn,stdout
log4j.logger.druid.sql.ResultSet=warn,stdout
10.5、輸出可執行的SQL
Java啟動參數配置方式
-Ddruid.log.stmt.executableSql=true
logFilter參數直接配置
<bean id="log-filter" class="com.alibaba.druid.filter.logging.Log4jFilter">
<property name="statementExecutableSqlLogEnable" value="true" />
</bean>
十一、Druid泄露
Druid提供了多種監測連接泄漏的手段
連接泄漏監測
當程序存在缺陷時,申請的連接忘記關閉,這時候,就存在連接泄漏了。Druid提供了RemoveAbandanded相關配置,用來關閉長時間不使用的連接。例如:
配置
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
... ...
<property name="removeAbandoned" value="true" />
<!-- 打開removeAbandoned功能 -->
<property name="removeAbandonedTimeout" value="1800" />
<!-- 1800秒,也就是30分鐘 -->
<property name="logAbandoned" value="true" />
<!-- 關閉abanded連接時輸出錯誤日誌 -->
... ...
</bean>
配置removeAbandoned對性能會有一些影響,建議懷疑存在泄漏之後再打開。在上面的配置中,如果連接超過30分鐘未關閉,就會被強行回收,並且日誌記錄連接申請時的調用堆棧。
內置監控頁面查看未關閉連接堆棧信息
當removeAbandoned=true之後,可以在內置監控界面datasource.html中的查看ActiveConnection StackTrace屬性的,可以看到未關閉連接的具體堆棧信息,從而方便查出哪些連接泄漏了。
web應用
如果你的應用配置了WebStatFilter
在內置監控頁面weburi-detail.html中,查看JdbcPoolConnectionOpenCount和JdbcPoolConnectionCloseCount屬性,如果不相等,就是泄漏了。
Druid連接池(二)