最近在做offerdetail優(yōu)化時,替換了數(shù)據(jù)庫驅(qū)動,從c3p0 0.9.1 -> dbcp 1.4,順便研究了下dbcp的自動重連的一套機(jī)制,也做一下分享,大家周知一下。
1. 數(shù)據(jù)庫意外重啟后,原先的數(shù)據(jù)庫連接池能自動廢棄老的無用的鏈接,建立新的數(shù)據(jù)庫鏈接
2. 網(wǎng)絡(luò)異常中斷后,原先的建立的tcp鏈接,應(yīng)該能進(jìn)行自動切換。比如網(wǎng)站演習(xí)中的交換機(jī)重啟會導(dǎo)致網(wǎng)絡(luò)瞬斷
3. 分布式數(shù)據(jù)庫中間件,比如cobar會定時的將空閑鏈接異常關(guān)閉,客戶端會出現(xiàn)半開的空閑鏈接。
1. sql心跳檢查(主動式)
2. 拿鏈接嘗試一下,發(fā)現(xiàn)處理失敗丟棄鏈接,探雷的請求會失敗幾個 (犧牲小我,完成大我的精神)
3. 設(shè)置合理的空閑鏈接的超時時間,避免半開鏈接(懶模式,解決半開鏈接)
下面我們來看看,在dbcp中是如何實現(xiàn)。
sql心跳檢查
<property name="testWhileIdle"><value>true</value></property>
<property name="testOnBorrow"><value>false</value></property>
<property name="testOnReturn"><value>false</value></property>
<property name="validationQuery"><value>select sysdate from dual</value></property>
<property name="validationQueryTimeout"><value>1</value></property>
<property name="timeBetweenEvictionRunsMillis"><value>30000</value></property>
<property name="numTestsPerEvictionRun"><value>16</value></property>
dbcp是采用了commons-pool做為其連接池管理,testOnBorrow,testOnReturn, testWhileIdle是pool是提供的幾種校驗機(jī)制,通過外部鉤子的方式回調(diào)dbcp的相關(guān)數(shù)據(jù)庫鏈接(validationQuery)校驗, dbcp相關(guān)外部鉤子類:PoolableConnectionFactory,繼承于common-pool PoolableObjectFactory , dbcp通過GenericObjectPool這一入口,進(jìn)行連接池的borrow,return處理。
具體參數(shù)描述:
1. testOnBorrow : 顧明思義,就是在進(jìn)行borrowObject進(jìn)行處理時,對拿到的connection進(jìn)行validateObject校驗
2. testOnReturn : 顧明思義,就是在進(jìn)行returnObject對返回的connection進(jìn)行validateObject校驗,個人覺得對數(shù)據(jù)庫連接池的管理意義不大
3. testWhileIdle : 關(guān)注的重點,GenericObjectPool中針對pool管理,起了一個異步Evict的TimerTask定時線程進(jìn)行控制(可通過設(shè)置參數(shù) timeBetweenEvictionRunsMillis>0),定時對線程池中的鏈接進(jìn)行validateObject校驗,對無效的鏈接進(jìn)行關(guān)閉后,會調(diào)用ensureMinIdle,適當(dāng)建立鏈接保證最小的minIdle連接數(shù)。
4. timeBetweenEvictionRunsMillis,設(shè)置的Evict線程的時間,單位ms,大于0才會開啟evict檢查線程
5. validateQuery, 代表檢查的sql
6. validateQueryTimeout, 代表在執(zhí)行檢查時,通過statement設(shè)置,statement.setQueryTimeout(validationQueryTimeout)
7. numTestsPerEvictionRun,代表每次檢查鏈接的數(shù)量,建議設(shè)置和maxActive一樣大,這樣每次可以有效檢查所有的鏈接.
1.性能問題。
目前網(wǎng)站的應(yīng)用大部分的瓶頸還是在I/O這一塊,大部分的I/O還是在數(shù)據(jù)庫的這一層面上,每一個請求可能會調(diào)用10來次SQL查詢,如果不走事務(wù),一個請求會重復(fù)獲取鏈接,如果每次獲取鏈接,比如在testOnBorrow都進(jìn)行validateObject,性能開銷不是很能接受,可以假定一次SQL操作消毫0.5~1ms(一般走了網(wǎng)絡(luò)請求基本就這數(shù))
2.成本和收益
網(wǎng)站異常數(shù)據(jù)庫重啟,網(wǎng)絡(luò)異常斷開的頻率是非常低的,一般也就在數(shù)據(jù)庫升級,演習(xí)維護(hù)時才會進(jìn)行,而且一般也是選在晚上,訪問量相對比較低的請求,而且一般會有人員值班關(guān)注,所以異步的validateObject是可以接受,但一個前提需要確保能保證在一個合理的時間段內(nèi),數(shù)據(jù)庫能完成自動重聯(lián)。
請求探雷
dbcp自身默認(rèn)支持,不需要配置
common-pools通過borrowObject , returnObject完成連接的獲取和釋放,正常的情況是一次請求中borrow和return是一對的,有借就有還。
但在準(zhǔn)備returnObject時,dbcp會做一件事,就是看看這個object是否已經(jīng)是壞了的,如果壞了就直接丟了,就直接給丟棄了。
代碼層面:
1. 在dbcp中PoolingDataSource(實現(xiàn)DataSource接口)調(diào)用 PoolableConnection(dbcp connnection相關(guān)的pool delegate操作)進(jìn)行相應(yīng)關(guān)閉時,會檢查_conn.isClosed(),針對DataSource如果isClosed返回為 true的則不調(diào)用returnObject,直接丟棄了鏈接。
2. _conn.isClosed()是否保險,從jdk的api描述中: A connection is closed if the method close has been called on it or if certain fatal errors have occurred. 里面提供兩種情況,一種就是被調(diào)用了closed方法,另一種就是出現(xiàn)一些異常,說的比較含糊。
空閑鏈接檢查
<property name="minEvictableIdleTimeMillis"><value>18000000</value></property>
<property name="removeAbandoned"><value>true</value></property>
<property name="removeAbandonedTimeout"><value>180</value></property>
1.minEvictableIdleTimeMillis dbcp默認(rèn)是30分,需要開啟異步線程Evict,否則不生效。原理很簡單,就是通過一個異步線程,每次檢查connnection上一次使用的時間戳,看看是否已經(jīng)超過這個timeout時間設(shè)置。
2. removeAbandoned , removeAbandonedTimeout,主要是用于在出現(xiàn)鏈接緊張時候,會掃描一些鏈接未超過removeAbandonedTimeout時間還未被釋放,會主動的關(guān)閉該鏈接。
1. 我們使用的cobar后端會有定時關(guān)閉空閑鏈接的操作,默認(rèn)的空閑鏈接timeout時間為1小時,和其他oracle , mysql各不相同,所以設(shè)置好這個空閑鏈接的timeout時間還是挺重要.
2. 一般會是幾種情況出現(xiàn)需要removeAbandoned:
* 代碼未在finally釋放connection , 不過我們都用sqlmapClientTemplate,底層都有鏈接釋放的過程
* 遇到數(shù)據(jù)庫死鎖。以前遇到過后端存儲過程做了鎖表操作,導(dǎo)致前臺集群中連接池全都被block住,后續(xù)的業(yè)務(wù)處理因為拿不到鏈接所有都處理失敗了。
還有我們配置的c3p0所謂的自動重連的3個參數(shù),
<prop key="acquireRetryAttempts">30</prop>
<prop key="acquireRetryDelay">1000</prop>
<prop key="breakAfterAcquireFailure">false</prop>
個人覺得就是一個誤導(dǎo),這幾個配置只是在從連接池獲取鏈接時,獲取失敗多嘗試幾次,因為我們從pool從獲取鏈接最多只會等待固定timeout時間。
如果要達(dá)到自動重連的效果,必須要c3p0支持請求探雷或者是sql心跳檢查功能,能自動的剔除無效的鏈接。
可見c3p0官方文檔描述:http://www.mchange.com/projects/c3p0/index.html#configuring_recovery
Dbcp將是我們以后數(shù)據(jù)庫驅(qū)動選擇的趨勢,最后我們?nèi)绾芜x擇如何自動重連,這個也得根據(jù)我們的應(yīng)用場景而定。比如只讀的web系統(tǒng),后臺業(yè)務(wù)系統(tǒng),任務(wù)系統(tǒng)可能處理方式就不同。
只讀Web系統(tǒng):可采取請求探雷的策略,也就失敗連接池個數(shù)的請求,失敗了頁面刷新一次就好。
后臺業(yè)務(wù)系統(tǒng):一般業(yè)務(wù)都涉及數(shù)據(jù)庫的寫操作,很多數(shù)據(jù)不可重入,一次處理失敗后就只能靠手工干預(yù)處理。這時候得考慮是否需要使用sql心跳檢查,比如testOnBorrow或者testWhileIdle.