druid連接泄露故障全面分析
1、問題的如何發(fā)生的
1.1、應(yīng)用功能介紹
系統(tǒng)是一個(gè)雙數(shù)據(jù)源雙寫單獨(dú)的服務(wù)。
(兩個(gè)數(shù)據(jù)源是不同的存儲(chǔ),所以無法使用主從復(fù)制的模式,是一個(gè)切換存儲(chǔ)介質(zhì)的過渡態(tài))。
歷史代碼有個(gè)更新邏輯update xx set a=b where m=n。
但是這個(gè)表中的記錄超10億。遇到需要更新的記錄比較多的場(chǎng)景下存在問題。
故對(duì)這個(gè)進(jìn)行了sql優(yōu)化。采用的邏輯是查詢出需要更新的記錄id,然后分頁更新。
1.2、關(guān)鍵代碼
雙數(shù)據(jù)源操作
private Object runSql(List<String> sqlSessionFactotyBeanNameList, MethodInvocation invocation)
throws InvocationTargetException, IllegalAccessException {
List<SqlSession> sqlSessionList = Lists.newArrayList();
Object result = null;
try {
for (String sessionFactotyBeanName : sqlSessionFactotyBeanNameList) {
SqlSessionFactory sqlSessionFactory =
RgApplicationContextUtil.getBean(
sessionFactotyBeanName, SqlSessionFactory.class);
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
Object mapper = sqlSession.getMapper(invocation.getMethod().getDeclaringClass());
Object[] param = invocation.getArguments();
result = invocation.getMethod().invoke(mapper, param);
sqlSessionList.add(sqlSession);//問題代碼,注意!!!!
sqlSession.commit();
}
} catch (Exception ex) {
sqlSessionList.stream()
.forEach(
x -> {
x.rollback();
});
} finally {
sqlSessionList.stream()
.forEach(
x -> {
x.close();
});
}
return result;
}
問題的sql
<select id="getBatchIdWithLimit" resultType="java.lang.Long">
SELECT x.id FROM context x WHERE x.oid = #{oid} ORDER BY id ASC
LIMIT #{offset}, #{limit}
</select>
關(guān)鍵的配置
maxWait 獲取連接時(shí)最大等待時(shí)間,單位毫秒。配置了maxWait之后,缺省啟用公平鎖,并發(fā)效率會(huì)有所下降,如果需要可以通過配置useUnfairLock屬性為true使用非公平鎖。
當(dāng)前系統(tǒng)此參數(shù)未進(jìn)行配置,所以會(huì)無限等待,使用的是公平鎖
1.3、問題出現(xiàn)的步驟
- sql中存在問題,部分?jǐn)?shù)據(jù)的長(zhǎng)度超過Integer的最大值(2147483647),映射存在問題。
- 雙數(shù)據(jù)源代碼存在bug。 List的代碼結(jié)合的 add 位置過于落后,導(dǎo)致反射出現(xiàn)異常的時(shí)候。當(dāng)次的SqlSession未關(guān)聯(lián)到待處理的集合中,進(jìn)而也就未rollback和close。造成鏈接泄露。
- 當(dāng)出現(xiàn)問題的數(shù)據(jù)的時(shí)候,結(jié)合雙數(shù)據(jù)源的代碼的bug。會(huì)造成List為空,所以未進(jìn)行釋放操作,(鏈接泄露了)
- 當(dāng)前系統(tǒng)最大的連接數(shù)是100,出現(xiàn)了100次這樣的數(shù)據(jù),這個(gè)服務(wù)就回?zé)o盡的等待獲取鏈接中的狀態(tài)。
1.4、問題的表象


2、如何復(fù)現(xiàn)問題
2.1、問題數(shù)據(jù)復(fù)現(xiàn)
- 把數(shù)據(jù)庫的最大連接數(shù)調(diào)整成1,maxWaitTime不設(shè)置
- 構(gòu)造一條id大于2147483647的數(shù)據(jù)
- 使用api 觸發(fā)調(diào)用到這個(gè)邏輯
- 結(jié)果是:第一次調(diào)用報(bào)錯(cuò),第二次調(diào)用會(huì)卡的客戶端設(shè)置的超時(shí)時(shí)間。
2.2、數(shù)據(jù)庫連接異常復(fù)現(xiàn)
還有一種路徑是代碼都沒問題,但是由于高并發(fā)造成數(shù)據(jù)庫是鎖。mybatis是可以設(shè)置sql的執(zhí)行時(shí)長(zhǎng)的。一旦出現(xiàn)了這種場(chǎng)景。問題也是會(huì)出現(xiàn)的。
但是這種場(chǎng)景比較難以復(fù)現(xiàn),那么有沒有一種手段可以高效的偽造這個(gè)場(chǎng)景。
準(zhǔn)備知識(shí)
set autocommit=0; //關(guān)閉數(shù)據(jù)的事務(wù)自動(dòng)提交 SELECT * FROM xxx a WHERE a.id='111' for update; //獲取數(shù)據(jù)庫的行鎖 commit;//提交事務(wù)
數(shù)據(jù)默認(rèn)是自動(dòng)提交的,所以前置set autocommit=0;這個(gè)操作不要忘記了,踩過幾次坑。完成后執(zhí)行commit;進(jìn)行解鎖。
測(cè)試完畢記得set autocommit=1;來恢復(fù)數(shù)據(jù)庫的事務(wù)自動(dòng)提交的特性。
- 準(zhǔn)備一條接口測(cè)試用的數(shù)據(jù)
- 執(zhí)行sql select …for update 進(jìn)行行記錄鎖定
- 接口調(diào)用使用同一個(gè)id進(jìn)行請(qǐng)求。因?yàn)橛涗涙i定了,所以api的更新是失敗了,成功的偽造了高并發(fā)形成了行鎖造成的sql問題
3、問題總結(jié)
- 數(shù)據(jù)庫的保護(hù)配置:maxActive、maxWait都配置上,相當(dāng)于熔斷保護(hù)
- mybatis對(duì)象映射需要關(guān)注數(shù)據(jù)的范圍
- 利用select for update制造行鎖偽造高并發(fā)造成的數(shù)據(jù)問題
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot實(shí)現(xiàn)統(tǒng)一封裝返回前端結(jié)果集的示例代碼
在實(shí)際項(xiàng)目開發(fā)過程中,我們經(jīng)常將返回?cái)?shù)據(jù)的基本形式統(tǒng)一為JSON格式的數(shù)據(jù)。但項(xiàng)目可能是由很多人開發(fā)的,所以我們最好將返回的結(jié)果統(tǒng)一起來。本文介紹了SpringBoot實(shí)現(xiàn)統(tǒng)一封裝返回前端結(jié)果集的示例代碼,需要的可以參考一下2022-06-06
在Java中如何避免創(chuàng)建不必要的對(duì)象
作為Java開發(fā)者,我們每天創(chuàng)建很多對(duì)象,但如何才能避免創(chuàng)建不必要的對(duì)象呢?這需要我們好好學(xué)習(xí),這篇文章主要給大家介紹了關(guān)于在Java中如何避免創(chuàng)建不必要對(duì)象的相關(guān)資料,需要的朋友可以參考下2021-10-10
java 調(diào)用wsdl協(xié)議接口簡(jiǎn)單實(shí)用方法最新推薦
文章介紹了如何使用POM導(dǎo)入依賴,并編寫一個(gè)測(cè)試類來調(diào)用不同的Web服務(wù)接口,通過訪問接口地址,我們可以獲取請(qǐng)求和返回的body,并進(jìn)一步解析返回的JSON結(jié)果,感興趣的朋友一起看看吧2025-03-03
記錄jdk21連接SQLServer因?yàn)門LS協(xié)議報(bào)錯(cuò)問題
在使用Druid連接池連接SQL Server時(shí),可能會(huì)遇到因TLS版本不匹配導(dǎo)致的連接失敗問題,具體表現(xiàn)為客戶端使用TLS1.3或TLS1.2,而SQL Server僅支持TLS1.0,導(dǎo)致無法建立安全連接,解決方法是修改JDK的安全配置,啟用TLS1.02024-10-10
Java學(xué)習(xí)關(guān)于循環(huán)和數(shù)組練習(xí)題整理
在本篇文章里小編給各位整理了關(guān)于Java學(xué)習(xí)關(guān)于循環(huán)和數(shù)組練習(xí)題相關(guān)內(nèi)容,有興趣的朋友們跟著參考學(xué)習(xí)下。2019-07-07
SpringCloud Netfilx Ribbon負(fù)載均衡工具使用方法介紹
Ribbon是Netflix的組件之一,負(fù)責(zé)注冊(cè)中心的負(fù)載均衡,有助于控制HTTP和TCP客戶端行為。Spring Cloud Netflix Ribbon一般配合Ribbon進(jìn)行使用,利用在Eureka中讀取的服務(wù)信息,在調(diào)用服務(wù)節(jié)點(diǎn)時(shí)合理進(jìn)行負(fù)載2022-12-12
詳解SpringBoot整合RabbitMQ如何實(shí)現(xiàn)消息確認(rèn)
這篇文章主要介紹了SpringBoot整合RabbitMQ是如何實(shí)現(xiàn)消息確認(rèn)的,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05

