1.問題描述
今天同事找到我,讓我幫忙查一個問題,據說已經持續一個月:
- 之前服務正常,問題在上個月開始出現。
- 服務運行大概1天左右,後臺開始報錯:獲取數據庫連接失敗
GetConnectionTimeoutException
。 - 服務配置的最大連接數
maxActive=600
。 - 但是,此時通過
show processlist
卻顯示此服務實際只佔用了300~400
左右的連接。 - 服務重啓,問題暫時消失。
2.報錯信息
下面是服務報錯信息:
- 替換掉了部分公司敏感信息。
- 出錯的sql隨機,可能是
UserDao.xml
相關的,也可能是OrderDao.xml
相關的。
2019-08-27 13:31:10,200 276111 ERROR pers.hanchao.server.UserServices:125 -
Exception: ### Error querying database. Cause: com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 10000, active 600, maxActive 600, creating 0
Exception: ### The error may exist in mybatis/UserDao.xml
Exception: ### The error may involve pers.hanchao.dao.PostExtraTitleDao.getByPostID
Exception: ### The error occurred while executing a query
Exception: ### Cause: com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 10000, active 600, maxActive 600, creating 0
Exception: org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 10000, active 600, maxActive 600, creating 0
### The error may exist in mybatis/PostExtraTitleDao.xml
### The error may involve pers.hanchao.dao.PostExtraTitleDao.getByPostID
### The error occurred while executing a query
### Cause: com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 10000, active 600, maxActive 600, creating 0
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:26)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:111)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:102)
at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:119)
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:63)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:52)
at com.sun.proxy.$Proxy54.getByID(Unknown Source)
...
3.問題分析
- 出錯的
sql
是隨機的,所以應該與業務邏輯無關。 druid
既然已經報錯active 600, maxActive 600
,則說明:當前druid
確實認爲還未釋放的連接有600
個。- 通過
show processlist
顯示實際連接300~400
個,則說明:當前服務實際存活的連接有300~400
個。 - 發送上述情況,應該是有代碼:
- 通過
druid
獲取了連接,但是未釋放連接,所以druid
認爲有600
個鏈接正在使用。 - 部分未釋放的連接在超過一定時間後,被
mysql
主動釋放,所以服務實際存活的連接有300~400
個。 - mysql釋放連接並不會告訴
druid
,所以druid
仍然認爲有600
個活連接。
- 通過
- 出問題的服務,有部分老代碼使用的是手動獲取和釋放
SqlSession
的邏輯,所以存在連接泄漏的可能。
4.問題定位
查詢全部代碼中獲取連接的地方,確定是否正確是否了連接。
正確寫法(僞代碼):
////方式一:try-catch主動close
public void func(){
SqlSession sqlSession = null;
try{
//從工具類獲取連接
sqlSession = getSqlSession();
//業務代碼...
}finally{
if(Objects.nonNull(sqlSession)){
sqlSession.close();
}
}
}
////方式二:try-resource自動close
public void func(){
try(SqlSession sqlSession = getSqlSession()){
//業務代碼...
}
}
錯誤寫法(僞代碼):
////錯誤一:只獲取不釋放
public void func(){
SqlSession sqlSession = getSqlSession();
//業務代碼...
}
////錯誤二:錯誤的try-catch釋放方式
//從工具類獲取連接
public void func(){
SqlSession sqlSession = getSqlSession();
try{
//業務代碼...
}finally{
if(Objects.nonNull(sqlSession)){
sqlSession.close();
}
}
}
5.問題解決
最終卻是發現一個緩存刷新服務,存在錯誤寫法,類似第4章的錯誤一:只獲取不釋放
。
這個服務,每5分鐘獲取1
個連接,1小時獲取12
個,1天獲取240
個鏈接,再加上真實存在的300~400
個連接,確實湊夠了600
個連接。
基本可以確認問題由此出代碼產生。
修改代碼,上線,問題解決。
6.問題總結
- 這個問題產生的原因有兩個:1.項目框架太老,需要手動釋放。2.開發人員不瞭解老框架。
- 本次問題解決比較順利,有時候好運氣也是解決問題的重要助力。