問題描述:自己負責的一個公司項目採用了JNDI數據源連接的方式部署在tomcat中,在tomcat中配置了數據源連接相關的屬性,最初配置如下:<Resource name="jdbc/myDB" auth="Container" type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000" username="root" password="123456" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://127.0.0.1:3306/mydb?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC"/>
同時項目也整合了hibernate框架,對hibernate相關屬性進行了配置。
tomcat啓動運行,數據庫連接正常使用,並沒有出現任何問題,持續了幾個月之久。
然而就在項目運行空閒期,中間大概間隔了幾天沒有任何請求調用,測試組需要驗證一些資源的可用度,於是對該項目接口進行了調用,發現出現服務異常的情況,經過一番查證,發現數據源連接出現問題,無法釋放。日誌如下:
org.springframework.dao.DataAccessResourceFailureException:could not extract ResultSet; nested exception isorg.hibernate.exception.JDBCConnectionException: could not extract ResultSet
atorg.springframework.orm.hibernate5.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:143)
atorg.springframework.orm.hibernate5.HibernateTemplate.doExecute(HibernateTemplate.java:344)
atorg.springframework.orm.hibernate5.HibernateTemplate.executeWithNativeSession(HibernateTemplate.java:309)
atorg.springframework.orm.hibernate5.HibernateTemplate.get(HibernateTemplate.java:419)
atorg.springframework.orm.hibernate5.HibernateTemplate.get(HibernateTemplate.java:412)
atcom.iflytek.zszb.dao.hbn.OmrPhotoDao.find(OmrPhotoDao.java:16)
atcom.iflytek.zszb.service.ASPProcessService.start(ASPProcessService.java:176)
atcom.iflytek.zszb.service.ASPProcessService.process(ASPProcessService.java:161)
atcom.iflytek.zszb.controller.ASPProcessController.entry(ASPProcessController.java:99)
atsun.reflect.GeneratedMethodAccessor43.invoke(Unknown Source)
atsun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
atjava.lang.reflect.Method.invoke(Method.java:606)
atorg.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
atorg.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
atorg.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
atorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:832)
atorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:743)
atorg.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
atorg.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:961)
atorg.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:895)
atorg.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967)
atorg.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:869)
atjavax.servlet.http.HttpServlet.service(HttpServlet.java:650)
atorg.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843)
atjavax.servlet.http.HttpServlet.service(HttpServlet.java:731)
atorg.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
atorg.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
atorg.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
atorg.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
atorg.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
atorg.springframework.orm.hibernate5.support.OpenSessionInViewFilter.doFilterInternal(OpenSessionInViewFilter.java:151)
atorg.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
atorg.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
atorg.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
atorg.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121)
atorg.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
atorg.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
atorg.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
atorg.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
atorg.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
atorg.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
atorg.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169)
atorg.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
atorg.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:956)
atorg.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
atorg.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:436)
atorg.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1078)
atorg.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:625)
atorg.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2517)
atorg.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2506)
atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
atorg.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
atjava.lang.Thread.run(Thread.java:745)
Caused by:org.hibernate.exception.JDBCConnectionException: could not extract ResultSet
atorg.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:115)
atorg.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
atorg.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:111)
atorg.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:97)
atorg.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:79)
atorg.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.getResultSet(AbstractLoadPlanBasedLoader.java:434)
atorg.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeQueryStatement(AbstractLoadPlanBasedLoader.java:186)
atorg.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:121)
atorg.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:86)
atorg.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader.load(AbstractLoadPlanBasedEntityLoader.java:167)
atorg.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:3991)
atorg.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:508)
atorg.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:478)
atorg.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219)
atorg.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:278)
atorg.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:121)
atorg.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89)
atorg.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1142)
atorg.hibernate.internal.SessionImpl.access$2600(SessionImpl.java:167)
atorg.hibernate.internal.SessionImpl$IdentifierLoadAccessImpl.doLoad(SessionImpl.java:2762)
atorg.hibernate.internal.SessionImpl$IdentifierLoadAccessImpl.load(SessionImpl.java:2741)
atorg.hibernate.internal.SessionImpl.get(SessionImpl.java:978)
atorg.springframework.orm.hibernate5.HibernateTemplate$1.doInHibernate(HibernateTemplate.java:426)
atorg.springframework.orm.hibernate5.HibernateTemplate.doExecute(HibernateTemplate.java:341)
...52 more
Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException:The last packet successfully received from the server was 106,759,000milliseconds ago. The last packet sentsuccessfully to the server was 106,759,000 milliseconds ago. is longer than theserver configured value of 'wait_timeout'. You should consider either expiringand/or testing connection validity before use in your application, increasingthe server configured values for client timeouts, or using the Connector/Jconnection property 'autoReconnect=true' to avoid this problem.
atsun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
atsun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
atsun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
atjava.lang.reflect.Constructor.newInstance(Constructor.java:526)
atcom.mysql.jdbc.Util.handleNewInstance(Util.java:404)
atcom.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:981)
atcom.mysql.jdbc.MysqlIO.send(MysqlIO.java:3652)
atcom.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2460)
atcom.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2625)
atcom.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2551)
atcom.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1861)
atcom.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:1962)
atorg.apache.tomcat.dbcp.dbcp.DelegatingPreparedStatement.executeQuery(DelegatingPreparedStatement.java:96)
atorg.apache.tomcat.dbcp.dbcp.DelegatingPreparedStatement.executeQuery(DelegatingPreparedStatement.java:96)
atorg.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:70)
...71 more
Caused by: java.net.SocketException:Software caused connection abort: socket write error
atjava.net.SocketOutputStream.socketWrite0(Native Method)
atjava.net.SocketOutputStream.socketWrite(SocketOutputStream.java:113)
atjava.net.SocketOutputStream.write(SocketOutputStream.java:159)
atjava.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
atjava.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
atcom.mysql.jdbc.MysqlIO.send(MysqlIO.java:3634)
...79 more
根據日誌描述來看,貌似是數據庫連接因爲多時未被使用或者被數據庫服務斷開導致已經失效,但是在tomcat的數據源池連接中並沒有移除,也就是說tomcat數據源連接池中的連接此時是無效的,一旦被調用如果沒有做檢查,就會出現如上問題。
查找了很多解決方案:
1. mysql.ini文件中wait_timeout屬性設置更長時間或者配置客戶端連接超時時間,這個沒試過,但是感覺有點不靠譜,畢竟並不能確定這些連接會被閒置多長時間然後被斷開;
2. 連接配置加autoReconnect=true屬性,測試沒有解決問題;
3. 在連接池配置中加上檢測連接的屬性,暫時可行。如下所示:
<Resource name="jdbc/myDB"
auth="Container"
type="javax.sql.DataSource"
maxActive="100"
maxIdle="30"
maxWait="10000"
testOnBorrow="true"
testWhileIdle="true"
validationQuery="select 1 fromdual"
username="root"
password="123456"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://127.0.0.1:3306/mydb?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC"/>
這樣,每次在使用連接的時候會去檢查數據庫連接對象的有效性,如果連接無效,則換另一個,同時會有語句select 1 from dual配合檢測。另外在連接空閒狀態會檢測,如果連接無效則drop掉,默認30分鐘檢測一次。對性能影響不是特別大。
測試流程:
1. 先將數據源按原始的配置,也就是沒有檢測屬性,運行項目進行接口調用,應該是沒有問題的;
2. 更改系統時間,將系統日期改成10天之後的日期,再進行調用,就能發現出現連接失效的問題了;
3. 關停tomcat進程,將數據連接檢測屬性加到數據源配置中,重啓tomcat,再重複1和2的流程,如果沒有出現連接失效問題,就OK了。
以上只是從應用層面作出一點小小的讓步,暫時解決這個問題,增加數據源連接對象檢測屬性配置,至少可以保證應用的穩定,不會因爲數據庫連接的問題導致整個應用不可用。
如果有更好的解決方案,還請路人大神不吝賜教,感謝。
tomcat數據源配置相關參數說明:
dataSource: 要連接的 datasource (通常我們不會定義在 server.xml)
defaultAutoCommit: 對於事務是否 autoCommit, 默認值爲 true
defaultReadOnly: 對於數據庫是否只能讀取, 默認值爲 false
driverClassName:連接數據庫所用的 JDBC Driver Class,
maxActive: 可以從對象池中取出的對象最大個數,爲0則表示沒有限制,默認爲8
maxIdle: 最大等待連接中的數量,設 0 爲沒有限制 (對象池中對象最大個數)
minIdle:對象池中對象最小個數
maxWait: 最大等待秒數, 單位爲 ms, 超過時間會丟出錯誤信息,-1爲無限制
password: 登陸數據庫所用的密碼
url: 連接數據庫的 URL
username: 登陸數據庫所用的帳號
validationQuery: 驗證連接是否成功, SQL SELECT 指令至少要返回一行
removeAbandoned: 是否自我中斷, 默認是 false
removeAbandonedTimeout: 幾秒後會自我中斷, removeAbandoned 必須爲 true
logAbandoned: 是否記錄中斷事件, 默認爲 false
minEvictableIdleTimeMillis:大於0 ,進行連接空閒時間判斷,或爲0,對空閒的連接不進行驗證;默認30分鐘
timeBetweenEvictionRunsMillis:失效檢查線程運行時間間隔,如果小於等於0,不會啓動檢查線程,默認-1
testOnBorrow:取得對象時是否進行驗證,檢查對象是否有效,默認爲false
testOnReturn:返回對象時是否進行驗證,檢查對象是否有效,默認爲false
testWhileIdle:空閒時是否進行驗證,檢查對象是否有效,默認爲false
Ø 在使用DBCP的時候,如果使用默認值,則數據庫連接因爲某種原因斷掉後,再從連接池中取得連接又不進行驗證,這時取得的連接實際上就會是無效的數據庫連接。因此爲了防止獲得的數據庫連接失效,在使用的時候最好保證:
username: 登陸數據庫所用的帳號
validationQuery:SELECT COUNT(*) FROM DUAL
testOnBorrow、testOnReturn、testWhileIdle:最好都設爲true
minEvictableIdleTimeMillis:大於0 ,進行連接空閒時間判斷,或爲0,對空閒的連接不進行驗證
timeBetweenEvictionRunsMillis:失效檢查線程運行時間間隔,如果小於等於0,不會啓動檢查線程
Ø PS:在構造GenericObjectPool [BasicDataSource在其createDataSource () 方法中也會使用GenericObjectPool]時,會生成一個內嵌類Evictor,實現自Runnable接口。如果 timeBetweenEvictionRunsMillis大於0,每過timeBetweenEvictionRunsMillis毫秒 Evictor會調用evict()方法,檢查對象的閒置時間是否大於minEvictableIdleTimeMillis毫秒(_minEvictableIdleTimeMillis小於等於0時則忽略,默認爲30分鐘),是則銷燬此對象,否則就激活並校驗對象,然後調用 ensureMinIdle方法檢查確保池中對象個數不小於_minIdle。在調用returnObject方法把對象放回對象池,首先檢查該對象是否有效,然後調用PoolableObjectFactory的passivateObject方法使對象處於非活動狀態。再檢查對象池中對象個數是否小於 maxIdle,是則可以把此對象放回對象池,否則銷燬此對象
Ø 上述特性的可設置性已在代碼中驗證,具體性能是否能實現有待實際驗證
removeAbandoned: 是否自我中斷, 默認是 false
removeAbandonedTimeout: 幾秒後會自我中斷, removeAbandoned 必須爲 true
logAbandoned: 是否記錄中斷事件, 默認爲 false