问题描述
公司一个新项目上线,处于试运行阶段,这个项目虽然是外网可访问的,故部署在了DMZ区,但试运行阶段只给了公司内少部分员工地址和账号(其中包括一些领导),故访问量很小,但项目还是挺重要的。
试运行阶段中,项目应用日志中不定期会报异常,尤其是在刚上午刚开始使用时,还有空闲一段时间后再次使用时,具体异常如下:
ERROR [com.alibaba.druid.util.JdbcUtils] - close connection error
java.sql.SQLRecoverableException: IO Error: Broken pipe
at oracle.jdbc.driver.T4CConnection.logoff(T4CConnection.java:556)
at oracle.jdbc.driver.PhysicalConnection.close(PhysicalConnection.java:3984)
at com.alibaba.druid.filter.FilterChainImpl.connection_close(FilterChainImpl.java:167)
at com.alibaba.druid.filter.stat.StatFilter.connection_close(StatFilter.java:254)
at com.alibaba.druid.filter.FilterChainImpl.connection_close(FilterChainImpl.java:163)
at com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl.close(ConnectionProxyImpl.java:115)
at com.alibaba.druid.util.JdbcUtils.close(JdbcUtils.java:79)
at com.alibaba.druid.pool.DruidDataSource.discardConnection(DruidDataSource.java:965)
at com.alibaba.druid.pool.DruidDataSource.getConnectionDirect(DruidDataSource.java:932)
at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:4534)
at com.alibaba.druid.filter.stat.StatFilter.dataSource_getConnection(StatFilter.java:661)
at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:4530)
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:884)
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:876)
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:92)
at org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:205)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:373)
at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:420)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:257)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy\$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:644)
at xxx.xx.modules.deposit.api.service.DepositApiService\$\$EnhancerBySpringCGLIB\$\$59c8f6e2.doRecharge()
at xxx.xx.modules.deposit.FundDepositController.rechargeConfirm(FundDepositController.java:125)
......
Caused by: java.net.SocketException: Broken pipe
at java.net.SocketOutputStream.socketWrite0(Native Method)
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:113)
at java.net.SocketOutputStream.write(SocketOutputStream.java:159)
at oracle.net.ns.DataPacket.send(DataPacket.java:210)
at oracle.net.ns.NetOutputStream.flush(NetOutputStream.java:230)
at oracle.net.ns.NetInputStream.getNextPacket(NetInputStream.java:312)
at oracle.net.ns.NetInputStream.read(NetInputStream.java:260)
at oracle.net.ns.NetInputStream.read(NetInputStream.java:185)
at oracle.net.ns.NetInputStream.read(NetInputStream.java:102)
at oracle.jdbc.driver.T4CSocketInputStreamWrapper.readNextPacket(T4CSocketInputStreamWrapper.java:124)
at oracle.jdbc.driver.T4CSocketInputStreamWrapper.read(T4CSocketInputStreamWrapper.java:80)
at oracle.jdbc.driver.T4CMAREngine.unmarshalUB1(T4CMAREngine.java:1137)
at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:290)
at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:192)
at oracle.jdbc.driver.T4C7Ocommoncall.doOLOGOFF(T4C7Ocommoncall.java:61)
at oracle.jdbc.driver.T4CConnection.logoff(T4CConnection.java:543)
... 69 more
从异常信息可以看出,问题是发生在Druid数据库连接池在关闭物理数据库连接时,报了 SocketException: Broken pipe,但为什么在使用时Druid会关闭数据库连接,关闭数据连接又为什么会报SocketException呢?这个异常到底对系统有多大的影响呢?下面一步步分析。
问题逐步分析 1、java.net.SocketException: Broken pipe异常是怎么产生的?有什么影响?项目中使用是的Druid连接数据库,可为什么在系统空闲一段时间后再使用,会尝试关闭数据库连接,而且关闭的时候还抛了 java.net.SocketException: Broken pipe 呢?
从异常堆栈信息,或者翻看Druid源码可以知道,异常是发生在从数据库连接池中获取连接,用于后续数据库操作时,在执行到DruidDataSource.getConnectionDirect(maxWaitMillis)方法时,有如下逻辑:
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException { //循环 for (;;) { //maxWaitMillis时间内从连接池获取一个连接 DruidPooledConnection poolalbeConnection = getConnectionInternal(maxWaitMillis); //testOnBorrow为true,即从池中获取连接后需要检查连接 if (isTestOnBorrow()) { boolean validate = testConnectionInternal(poolalbeConnection.getConnection()); if (!validate) { if (LOG.isDebugEnabled()) { LOG.debug("skip not validate connection."); } Connection realConnection = poolalbeConnection.getConnection(); discardConnection(realConnection); continue; } } else { Connection realConnection = poolalbeConnection.getConnection(); //如果连接已经关闭,再从池中获取一个 if (realConnection.isClosed()) { discardConnection(null); // 传入null,避免重复关闭 continue; } //testWhileIdle为true,即空闲后需要检查连接 if (isTestWhileIdle()) { //连接空闲时间(当前时间 - 上次ActiveTime) long idleMillis = System.currentTimeMillis() - poolalbeConnection.getConnectionHolder().getLastActiveTimeMillis(); //连接空闲时间 > timeBetweenEvictionRunsMillis,检查连接 if (idleMillis >= this.getTimeBetweenEvictionRunsMillis()) { boolean validate = testConnectionInternal(poolalbeConnection.getConnection()); //连接检查失败,打印log,丢弃连接,再获取一个连接 if (!validate) { if (LOG.isDebugEnabled()) { LOG.debug("skip not validate connection."); } discardConnection(realConnection); continue; } } } } //如果开启了连接超时回收 if (isRemoveAbandoned()) { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); poolalbeConnection.setConnectStackTrace(stackTrace); poolalbeConnection.setConnectedTimeNano(); //设置当前时间为ConnectedTime poolalbeConnection.setTraceEnable(true); synchronized (activeConnections) { activeConnections.put(poolalbeConnection, PRESENT); //将连接放入activeConnections Map } } if (!this.isDefaultAutoCommit()) { poolalbeConnection.setAutoCommit(false); } return poolalbeConnection; } }
简单来说,在从Druid获取数据库连接时,可以进行test,这段代码中包含testOnBorrow(借出时检查)和testWhileIdle(空闲时检查)的逻辑,此项目在配置文件中
testOnBorrow = false
testWhileIdle = true
timeBetweenEvictionRunsMillis = 60000(60s)