精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

使用Arthas一步步分析druid連接池Bug

開發 前端
如果沒有出現在【抓取的關閉PgConnection的本地端口號】中,那么這個本地端口號對應的連接就是沒有被關閉的,通過與netstat查的端口號對比,是一致的,所以猜測沒有調用到PgConnection.close或者調用PgConnection.close出現異常了?

最近項目組某應用將數據庫由Oracle切換到了TBase,遇到了數據庫連接泄露導致無法創建新連接的問題,下面是問題的分析過程。

問題現象

應用側異常日志

為了便于閱讀,去掉了線程棧中不相關的棧幀。

com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 5000, active 0, maxActive 30, creating 0, createErrorCount 13047 
	at com.alibaba.druid.pool.DruidDataSource.getConnectionInternal(DruidDataSource.java:1773) 
	at com.alibaba.druid.pool.DruidDataSource.getConnectionDirect(DruidDataSource.java:1427) 
	at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:5059) 
	at com.alibaba.druid.filter.logging.LogFilter.dataSource_getConnection(LogFilter.java:917) 
	at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:5055) 
	at com.alibaba.druid.filter.stat.StatFilter.dataSource_getConnection(StatFilter.java:726) 
	at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:5055) 
	at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1405) 
	at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1397) 
	at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:100) 
	at org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:204) 
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:373) 
	at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:430) 
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:276) 
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) 
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) 
	... ...//省略的棧幀
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) 
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) 
at java.lang.Thread.run(Thread.java:745) Caused by: org.postgresql.util.PSQLException: FATAL: remaining connection slots are reserved for non-replication superuser connections 
	at org.postgresql.core.v3.ConnectionFactoryImpl.doAuthentication(ConnectionFactoryImpl.java:693) 
	at org.postgresql.core.v3.ConnectionFactoryImpl.tryConnect(ConnectionFactoryImpl.java:203) 
	at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:258) 
	at org.postgresql.core.ConnectionFactory.openConnection(ConnectionFactory.java:54) 
	at org.postgresql.jdbc.PgConnection.<init>(PgConnection.java:263) 
	at org.postgresql.Driver.makeConnection(Driver.java:443) 
	at org.postgresql.Driver.connect(Driver.java:297) 
	at com.alibaba.druid.filter.FilterChainImpl.connection_connect(FilterChainImpl.java:156) 
	at com.alibaba.druid.filter.FilterAdapter.connection_connect(FilterAdapter.java:787) 
	at com.alibaba.druid.filter.FilterEventAdapter.connection_connect(FilterEventAdapter.java:38) 
	at com.alibaba.druid.filter.FilterChainImpl.connection_connect(FilterChainImpl.java:150) 
	at com.alibaba.druid.filter.stat.StatFilter.connection_connect(StatFilter.java:251) 
	at com.alibaba.druid.filter.FilterChainImpl.connection_connect(FilterChainImpl.java:150) 
	at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1659) 
	at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1723) 
	at com.alibaba.druid.pool.DruidDataSource$CreateConnectionThread.run(DruidDataSource.java:2838)

從上面線程棧可知,org.postgresql.util.PSQLException導致了com.alibaba.druid.pool.GetConnectionTimeoutException,所以org.postgresql.util.PSQLException是根因:FATAL: remaining connection slots are reserved for non-replication superuser connections,該異常表示TBase Server端該用戶的連接數達到了閾值。接下來看一下TBase服務端連接數的情況。

TBase服務端連接數

指標數據

從上圖可知

  • 由于達到了TBase Server端連接數2000的閾值,所以導致了應用無法創建連接的異常;
  • 隨著應用運行,連接數在慢慢增加,很明顯存在連接泄露的問題。

基本信息

應用使用的druid連接池,版本是druid-1.2.8;TBase JDBC Driver是postgresql-42.6.0;TBase Server版本是10.0 @ Tbase_v5.06.4.3 (commit: 6bd7f61dc)。JDK版本:

java version "1.8.0_65"
Java(TM) SE Runtime Environment (build 1.8.0_65-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.65-b01, mixed mode)

druid配置

druid配置包括兩部分,分別是:Filter、DataSource。

Filter

<bean id="filter-log4j2" class="com.alibaba.druid.filter.logging.Log4j2Filter">
    <property name="connectionLogEnabled" value="true" />
    <property name="statementLogEnabled" value="true" />
    <property name="statementExecutableSqlLogEnable" value="true" />
    <property name="resultSetLogEnabled" value="false" />
</bean>

DataSource

<bean id="dataSource6" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
		<property name="driverClassName" value="org.postgresql.Driver" />
		<property name="url" value="" />
		<property name="xxx" value="" />
		<property name="xxx" value="" />
		
		<property name="initialSize" value="2" />
		<property name="minIdle" value="5" />
		<property name="maxActive" value="30" />
		<property name="maxWait" value="5000" />
		
		<property name="timeBetweenEvictionRunsMillis" value="60000" />
		<property name="minEvictableIdleTimeMillis" value="300000" />
		
		<property name="testWhileIdle" value="true" />
		<property name="testOnBorrow" value="false" />
		<property name="testOnReturn" value="false" />
		
		<property name="phyTimeoutMillis" value="1800000" />
		
		<property name="poolPreparedStatements" value="false" />
		<property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
		
		<!-- 對泄漏的連接 自動關閉 -->
		<!-- 打開removeAbandoned功能 -->
		<property name="removeAbandoned" value="true" />
		<!-- 60秒,也就是1分鐘 -->
		<property name="removeAbandonedTimeout" value="60" />
		<!-- 關閉abanded連接時輸出錯誤日志 -->
		<property name="logAbandoned" value="true" />
		
		<!-- 配置監控統計攔截的filters -->
		<property name="filters" value="mergeStat" />
		<property name="proxyFilters">
				<list>
						<ref bean="filter-log4j2" />
				</list>
		</property>
		
		<property name="connectionProperties" value="druid.stat.slowSqlMillis=5000" />
</bean>

問題分析

removeAbandoned

數據庫連接泄露,首先想到的是應用程序從數據庫連接池獲取到連接,使用完之后是否沒有釋放?從上面的異常信息看是不存在這種情況的,因為active是0(即當前正在使用的連接數是0),為了便于理解,下面分析下druid這塊的邏輯。從上面可知,druid removeAbandoned相關配置為:

<!-- 對泄漏的連接 自動關閉 -->
<!-- 打開removeAbandoned功能 -->
<property name="removeAbandoned" value="true" />
<!-- 60秒,也就是1分鐘 -->
<property name="removeAbandonedTimeout" value="60" />
<!-- 關閉abanded連接時輸出錯誤日志 -->
<property name="logAbandoned" value="true" />

申請連接

以下代碼在DruidDataSource類的getConnectionDirect方法中。

DruidPooledConnection poolableConnection ;
... ...
if (removeAbandoned) {
    StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
    poolableConnection.connectStackTrace = stackTrace;
    poolableConnection.setConnectedTimeNano();
    poolableConnection.traceEnable = true;

    activeConnectionLock.lock();
    try {
        activeConnections.put(poolableConnection, PRESENT);
    } finally {
        activeConnectionLock.unlock();
    }
}

釋放連接

以下代碼在DruidDataSource類的recycle方法中,該方法是在應用程序將連接歸還給連接池的時候調用。

DruidPooledConnection poolableConnection ;
... ...
if (pooledConnection.traceEnable) {
    Object oldInfo = null;
    activeConnectionLock.lock();
    try {
        if (pooledConnection.traceEnable) {
            oldInfo = activeConnections.remove(pooledConnection);
            pooledConnection.traceEnable = false;
        }
    } finally {
        activeConnectionLock.unlock();
    }
    if (oldInfo == null) {
        if (LOG.isWarnEnabled()) {
            LOG.warn("remove abandonded failed. activeConnections.size " + activeConnections.size());
        }
    }
}

如果應用程序沒有釋放連接,activeConnections會有未釋放連接的引用。

釋放泄露的連接

DruidDataSource中有個Druid-ConnectionPool-Destroy-XX的線程負責釋放連接:

protected void createAndStartDestroyThread() {
    destroyTask = new DestroyTask();

    if (destroyScheduler != null) {
        long period = timeBetweenEvictionRunsMillis;
        if (period <= 0) {
            period = 1000;
        }
        destroySchedulerFuture = destroyScheduler.scheduleAtFixedRate(destroyTask, period, period,
                                                                      TimeUnit.MILLISECONDS);
        initedLatch.countDown();
        return;
    }

    String threadName = "Druid-ConnectionPool-Destroy-" + System.identityHashCode(this);
    destroyConnectionThread = new DestroyConnectionThread(threadName);
    destroyConnectionThread.start();
}

因為destroyScheduler是null,所以使用的是DestroyConnectionThread,每隔timeBetweenEvictionRunsMillis執行一次釋放任務。

public class DestroyConnectionThread extends Thread {
    public DestroyConnectionThread(String name){
        super(name);
        this.setDaemon(true);
    }
    public void run() {
        initedLatch.countDown();
        for (;;) {
            // 從前面開始刪除
            try {
                if (closed || closing) {
                    break;
                }
                if (timeBetweenEvictionRunsMillis > 0) {
                    Thread.sleep(timeBetweenEvictionRunsMillis);
                } else {
                    Thread.sleep(1000); //
                }
                if (Thread.interrupted()) {
                    break;
                }
                destroyTask.run();
            } catch (InterruptedException e) {
                break;
            }
        }
    }
}
public class DestroyTask implements Runnable {
    public DestroyTask() {
    }

    @Override
    public void run() {
        shrink(true, keepAlive);

        if (isRemoveAbandoned()) {
            removeAbandoned();
        }
    }
}
public int removeAbandoned() {
    int removeCount = 0;
    long currrentNanos = System.nanoTime();
    List<DruidPooledConnection> abandonedList = new ArrayList<DruidPooledConnection>();
    activeConnectionLock.lock();
    try {
        Iterator<DruidPooledConnection> iter = activeConnections.keySet().iterator();
        for (; iter.hasNext();) {
            DruidPooledConnection pooledConnection = iter.next();
            if (pooledConnection.isRunning()) {
                continue;
            }
            long timeMillis = (currrentNanos - pooledConnection.getConnectedTimeNano()) / (1000 * 1000);
            if (timeMillis >= removeAbandonedTimeoutMillis) {
                iter.remove();
                pooledConnection.setTraceEnable(false);
                abandonedList.add(pooledConnection);
            }
        }
    } finally {
        activeConnectionLock.unlock();
    }

    if (abandonedList.size() > 0) {
        for (DruidPooledConnection pooledConnection : abandonedList) {
            final ReentrantLock lock = pooledConnection.lock;
            lock.lock();
            try {
                if (pooledConnection.isDisable()) {
                    continue;
                }
            } finally {
                lock.unlock();
            }
        	//釋放連接
            JdbcUtils.close(pooledConnection);
            pooledConnection.abandond();
            removeAbandonedCount++;
            removeCount++;
        	//打印日志
            if (isLogAbandoned()) {
                StringBuilder buf = new StringBuilder();
                buf.append("abandon connection, owner thread: ");
                buf.append(pooledConnection.getOwnerThread().getName());
                buf.append(", connected at : ");
                buf.append(pooledConnection.getConnectedTimeMillis());
                buf.append(", open stackTrace\n");

                StackTraceElement[] trace = pooledConnection.getConnectStackTrace();
                for (int i = 0; i < trace.length; i++) {
                    buf.append("\tat ");
                    buf.append(trace[i].toString());
                    buf.append("\n");
                }

                buf.append("ownerThread current state is " + pooledConnection.getOwnerThread().getState()
                           + ", current stackTrace\n");
                trace = pooledConnection.getOwnerThread().getStackTrace();
                for (int i = 0; i < trace.length; i++) {
                    buf.append("\tat ");
                    buf.append(trace[i].toString());
                    buf.append("\n");
                }

                LOG.error(buf.toString());
            }
        }
    }

    return removeCount;
}

從以上分析可知,即使應用沒有釋放連接,Druid-ConnectionPool-Destroy線程也會釋放掉activeConnections中的連接(除非業務線程一直阻塞,連接處于running==true的狀態,通過分析線程棧不存在這種情況)。

泄露的連接不是通過連接池申請的?

另一個可能的原因是泄露的連接不是通過連接池申請的,而是應用自己創建出來,但是沒有執行關閉連接的操作。所以思路是拿到所有創建連接的線程棧,然后通過分析線程棧找到有哪些地方創建的連接,這里我們使用arthas的watch來完成。

//抓取PgConnection本地端口號和線程棧
watch org.postgresql.core.v3.ConnectionFactoryImpl openConnectionImpl "{returnObj.pgStream.connection.impl.localport,@org.apache.commons.lang3.exception.ExceptionUtils@getStackTrace(new java.lang.Exception())}" -n 99999999 > /xxx/xxx/xxx/xxx.txt &

TBase的數據庫連接對象是org.postgresql.jdbc.PgConnection,在PgConnection初始化的時候會調用org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl。生成的文件中記錄了每個數據庫連接的【本地端口號】和【線程棧】,通過對線程棧進行分析,發現數據庫連接都是通過連接池創建出來的。下面是創建數據庫連接的線程棧:

at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:330)
at org.postgresql.core.ConnectionFactory.openConnection(ConnectionFactory.java:54)
at org.postgresql.jdbc.PgConnection.<init>(PgConnection.java:263)
at org.postgresql.Driver.makeConnection(Driver.java:443)
at org.postgresql.Driver.connect(Driver.java:297)
at com.alibaba.druid.filter.FilterChainImpl.connection_connect(FilterChainImpl.java:156)
at com.alibaba.druid.filter.FilterAdapter.connection_connect(FilterAdapter.java:787)
at com.alibaba.druid.filter.FilterEventAdapter.connection_connect(FilterEventAdapter.java:38)
at com.alibaba.druid.filter.FilterChainImpl.connection_connect(FilterChainImpl.java:150)
at com.alibaba.druid.filter.stat.StatFilter.connection_connect(StatFilter.java:251)
at com.alibaba.druid.filter.FilterChainImpl.connection_connect(FilterChainImpl.java:150)
at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1659)
at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1723)
at com.alibaba.druid.pool.DruidDataSource$CreateConnectionThread.run(DruidDataSource.java:2838)

既然數據庫連接都是通過連接池創建出來的,那么數據庫連接應該都是受到連接池管理的。

數據庫連接池連接與netstat看到的不一致

既然連接都是連接池管理的,那么分析下連接池中的連接信息:

連接池中連接信息

//查看連接池信息
vmtool --action getInstances --className com.alibaba.druid.pool.DruidDataSource --express 'instances.{? #this.dbTypeName=="postgresql"}.{#this}'
//查看連接池中連接本地端口號
vmtool --action getInstances --className com.alibaba.druid.pool.DruidDataSource --express 'instances.{? #this.dbTypeName=="postgresql"}.{#this.connections[0].conn.connection.getQueryExecutor().pgStream.connection.impl.localport}' -x 1

指標數據

netstat連接信息

網絡連接

netstat共查出8個連接,其中只有33946和33324是在連接池中的,其他6個都不在連接池中。分析到這里,可以確定存在連接泄露,即有連接沒有被關閉的情況,那么哪些地方進行了連接關閉呢?

連接關閉的路徑有哪些?

上面已經抓取到了每個PgConnection的創建路徑,現在抓取每個PgConnection的關閉路徑,如果一個PgConnection有創建但是沒有關閉,那么這個PgConnection就是泄露的連接。

//抓取PgConnection本地端口號和線程棧
watch org.postgresql.jdbc.PgConnection close "{target.getQueryExecutor().pgStream.connection.impl.localport,@org.apache.commons.lang3.exception.ExceptionUtils@getStackTrace(new java.lang.Exception()),throwExp}" -b -n 99999999 > /xxx/xxx/xxx/xxx.txt &

通過分析以上抓取的數據,關閉連接主要有兩條路徑:通過業務路徑:

at org.postgresql.jdbc.PgConnection.close(PgConnection.java:857)
at com.alibaba.druid.filter.FilterChainImpl.connection_close(FilterChainImpl.java:186)
at com.alibaba.druid.filter.FilterAdapter.connection_close(FilterAdapter.java:777)
at com.alibaba.druid.filter.logging.LogFilter.connection_close(LogFilter.java:481)
at com.alibaba.druid.filter.FilterChainImpl.connection_close(FilterChainImpl.java:181)
at com.alibaba.druid.filter.stat.StatFilter.connection_close(StatFilter.java:294)
at com.alibaba.druid.filter.FilterChainImpl.connection_close(FilterChainImpl.java:181)
at com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl.close(ConnectionProxyImpl.java:114)
at com.alibaba.druid.util.JdbcUtils.close(JdbcUtils.java:85)
at com.alibaba.druid.pool.DruidDataSource.discardConnection(DruidDataSource.java:1546)
at com.alibaba.druid.pool.DruidDataSource.recycle(DruidDataSource.java:2011)
at com.alibaba.druid.pool.DruidPooledConnection.recycle(DruidPooledConnection.java:351)
at com.alibaba.druid.filter.FilterChainImpl.dataSource_recycle(FilterChainImpl.java:5049)
at com.alibaba.druid.filter.logging.LogFilter.dataSource_releaseConnection(LogFilter.java:907)
at com.alibaba.druid.filter.FilterChainImpl.dataSource_recycle(FilterChainImpl.java:5045)
at com.alibaba.druid.filter.stat.StatFilter.dataSource_releaseConnection(StatFilter.java:711)
at com.alibaba.druid.filter.FilterChainImpl.dataSource_recycle(FilterChainImpl.java:5045)
at com.alibaba.druid.pool.DruidPooledConnection.syncClose(DruidPooledConnection.java:324)
at com.alibaba.druid.pool.DruidPooledConnection.close(DruidPooledConnection.java:270)
at org.springframework.jdbc.datasource.DataSourceUtils.doCloseConnection(DataSourceUtils.java:341)
at org.springframework.jdbc.datasource.DataSourceUtils.doReleaseConnection(DataSourceUtils.java:328)
at org.springframework.jdbc.datasource.DataSourceUtils.releaseConnection(DataSourceUtils.java:294)
at org.springframework.jdbc.datasource.DataSourceTransactionManager.doCleanupAfterCompletion(DataSourceTransactionManager.java:329)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.cleanupAfterCompletion(AbstractPlatformTransactionManager.java:1016)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:811)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:487)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:291)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
at com.sun.proxy.$Proxy303.xxxxxxx業務方法(Unknown Source)
at sun.reflect.GeneratedMethodAccessor149.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.taobao.hsf.remoting.provider.ReflectInvocationHandler.handleRequest0(ReflectInvocationHandler.java:83)

通過druid Destroy線程路徑:

at org.postgresql.jdbc.PgConnection.close(PgConnection.java:857)
at com.alibaba.druid.filter.FilterChainImpl.connection_close(FilterChainImpl.java:186)
at com.alibaba.druid.filter.FilterAdapter.connection_close(FilterAdapter.java:777)
at com.alibaba.druid.filter.logging.LogFilter.connection_close(LogFilter.java:481)
at com.alibaba.druid.filter.FilterChainImpl.connection_close(FilterChainImpl.java:181)
at com.alibaba.druid.filter.stat.StatFilter.connection_close(StatFilter.java:294)
at com.alibaba.druid.filter.FilterChainImpl.connection_close(FilterChainImpl.java:181)
at com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl.close(ConnectionProxyImpl.java:114)
at com.alibaba.druid.util.JdbcUtils.close(JdbcUtils.java:85)
at com.alibaba.druid.pool.DruidDataSource.shrink(DruidDataSource.java:3194)
at com.alibaba.druid.pool.DruidDataSource$DestroyTask.run(DruidDataSource.java:2938)
at com.alibaba.druid.pool.DruidDataSource$DestroyConnectionThread.run(DruidDataSource.java:2922)

【抓取到的創建PgConnection的本地端口號】如果沒有出現在【抓取的關閉PgConnection的本地端口號】中,那么這個本地端口號對應的連接就是沒有被關閉的,通過與netstat查的端口號對比,是一致的,所以猜測沒有調用到PgConnection.close或者調用PgConnection.close出現異常了?

連接池到底做沒做釋放連接的操作?

以下分別從宏觀統計數據和單個未關閉連接的角度分析:數據庫連接池到底做沒做釋放連接的操作。

宏觀統計數據

druid記錄了很多指標數據:

vmtool --action getInstances --className com.alibaba.druid.pool.DruidDataSource --express 'instances.{? #this.dbTypeName=="postgresql"}.{#this}'
vmtool --action getInstances --className com.alibaba.druid.pool.DruidDataSource --express 'instances.{? #this.dbTypeName=="postgresql"}.{#this.discardCount}'

指標數據

圖上幾個關鍵字段說明如下:

序號

字段

說明

1

ActiveCount

當前正在被應用程序使用中的連接數量

2

PoolingCount

當前連接池中空閑的連接數量

3

CreateCount

連接池創建過的物理連接的數量

4

DestroyCount

連接池關閉過的物理連接的數量

5

ConnectCount

應用向連接池申請過的邏輯連接的數量

6

CloseCount

應用向連接池釋放過的邏輯連接的數量

7

discardCount

關閉過的物理連接的數量,具體邏輯可以查看discardConnection和shrink方法中的邏輯

這里我們分析兩個數據,分別是:

物理連接創建和關閉

通過對druid邏輯分析,得出公式:CreateCount = ActiveCount + PoolingCount + DestroyCount + discardCount,將數據代入以上公式,即:243 = 0 + 2 + 107 + 134。

邏輯連接申請和釋放

通過對druid邏輯分析,得出公式:ConnectCount = ActiveCount + CloseCount + discardCount,將數據代入以上公式,即:212586 = 0 + 212452 + 134。通過以上分析,從連接池的角度是已經釋放連接了。

單個未關閉連接的角度

首先需要弄清楚連接池中幾個關鍵對象的關系,當應用向連接池申請連接的時候,連接池返回給應用的是DruidPooledConnection;當應用歸還連接到連接池的時候,調用的是DruidPooledConnection.close();某些條件下連接會被關閉。

應用向連接池申請連接

申請連接

應用向連接池歸還連接

釋放連接

歸還連接的時候,代碼不會走到com.alibaba.druid.util.JdbcUtils.close的邏輯。

關閉連接

關閉連接

如果一個PgConnection被關閉,那么與該PgConnection之前有關聯的對象的狀態會有變化,下面我們分析下與本地端口號是34802的PgConnection有關系的對象的狀態。

DruidPooledConnection

vmtool --action getInstances --className com.alibaba.druid.pool.DruidPooledConnection --express 'instances.{? #this.conn!=null && #this.conn.dataSource.dbTypeName=="postgresql" && #this.conn.connection.getQueryExecutor().pgStream.connection.impl.localport==34802}.{#this.disable}' --limit 200000 -x 2

沒有找到對應的對象,說明已經調用了DruidPooledConnection.close(),因為調用close后,會設置該對象的conn為null。

DruidConnectionHolder

vmtool --action getInstances --className com.alibaba.druid.pool.DruidConnectionHolder --express 'instances.{? #this.dataSource.dbTypeName=="postgresql" && #this.conn.connection.getQueryExecutor().pgStream.connection.impl.localport==34802}.{#this.discard}' --limit 20000

DruidConnectionHolder.discard=false,因為連接的關閉有兩條路徑:

  • 通過DestroyTask.run路徑關閉,不會設置DruidConnectionHolder.discard為true,分析發現泄露連接的discard字段值都為false,所以泄露的連接是通過該路徑執行的關閉
  • 通過DruidPooledConnection.close()路徑關閉,會設置DruidConnectionHolder.discard為true

ConnectionProxyImpl

@Override
public void close() throws SQLException {
    FilterChainImpl chain = createChain();
    chain.connection_close(this);
    closeCount++;
    recycleFilterChain(chain);
}
vmtool --action getInstances --className com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl --express 'instances.{? #this.dataSource.dbTypeName=="postgresql" && #this.connection.getQueryExecutor().pgStream.connection.impl.localport==34802}.{#this.closeCount}' --limit 20000

ConnectionProxyImpl.closeCount=0,如果連接池正確關閉的話應該等于1。

PgConnection

@Override
public void close() throws SQLException {
    if (queryExecutor == null) {
      return;
    }
    if (queryExecutor.isClosed()) {
      return;
    }
    releaseTimer();
    queryExecutor.close();
    openStackTrace = null;
}
// queryExecutor.close()
@Override
public void close() {
    if (closed) {
      return;
    }
    
    try {
      LOGGER.log(Level.FINEST, " FE=> Terminate");
      sendCloseMessage();
      pgStream.flush();
      pgStream.close();
    } catch (IOException ioe) {
      LOGGER.log(Level.FINEST, "Discarding IOException on close:", ioe);
    }
    closed = true;
}
// pgStream.close()
@Override
public void close() throws IOException {
    if (encodingWriter != null) {
      encodingWriter.close();
    }
    
    pgOutput.close();
    pgInput.close();
    connection.close();
}

PgConnection.isClosed()=false,如果連接正確關閉的話應該等于true。

繼續分析

從以上分析可知,泄露連接DruidConnectionHolder.discard都為false,所以發現問題的代碼大概率出現在DestroyTask.run()方法中:該方法主要包括shrink方法和removeAbandoned方法,其中removeAbandoned方法已經在前面分析過,下面主要分析下shrink邏輯,shrink邏輯主要包括(我們的配置不涉及keepAlive處理邏輯):【篩選出evictConnections和keepAliveConnections】、【從connections中去掉evictConnections和keepAliveConnections的連接】、【處理evictConnections和keepAliveConnections】。

連接池刪除了連接,但是連接沒close掉?

最開始懷疑問題出在【處理evictConnections和keepAliveConnections】,即在關閉連接的時候出現了異常,雖然連接池中刪除了該連接,但是close該連接的時候并沒有關閉干凈,所以通過arthas watch抓取close階段是否出現異常:

watch com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl close "{target.connection.getQueryExecutor().pgStream.connection.impl.localport,@org.apache.commons.lang3.exception.ExceptionUtils@getStackTrace(new java.lang.Exception())}" 'target.dataSource.dbTypeName=="postgresql"' -e > /xxx/xxx/xxx/error.txt &

結果沒有抓到。另外從PgConnection角度分析,如果PgConnection調用了close方法,那么PgConnection相關聯的org.postgresql.core.QueryExecutorCloseAction類的PG_STREAM_UPDATER會變為null,而PG_STREAM_UPDATER并沒有變為null。下面是QueryExecutorCloseAction中的方法:

@Override
public void close() throws IOException {
    ... ...
    PGStream pgStream = this.pgStream;
    if (pgStream == null || !PG_STREAM_UPDATER.compareAndSet(this, pgStream, null)) {
      // The connection has already been closed
      return;
    }
    sendCloseMessage(pgStream);
    if (pgStream.isClosed()) {
      return;
    }
    pgStream.flush();
    pgStream.close();
}

總上分析,泄露的連接壓根就沒有調用其close方法。

難道在【從connections中去掉evictConnections和keepAliveConnections的連接】階段把連接弄丟了?

通過分析代碼及翻找druid issues,出問題的代碼主要在:

int removeCount = evictCount + keepAliveCount;
if (removeCount > 0) {
    System.arraycopy(connections, removeCount, connections, 0, poolingCount - removeCount);
    Arrays.fill(connections, poolingCount - removeCount, poolingCount, null);
    poolingCount -= removeCount;
}

問題大概是這樣子的:1.假設connections中有四個連接,分別是1001、1002、1003、1004,其中1001、1003被確認是evictConnection

2.去除connections中的evictConnection

從上面圖中可以看出,最終把1002連接給弄丟了,所以該連接也不會再調用到close方法,導致了連接泄露。

解決辦法

從druid 1.2.18開始對shrink邏輯進行了優化,可以嘗試切換到新版本

責任編輯:武曉燕 來源: 今日頭條
相關推薦

2016-11-02 18:54:01

javascript

2017-01-19 21:08:33

iOS路由構建

2018-12-24 10:04:06

Docker存儲驅動

2019-07-09 15:23:22

Docker存儲驅動

2019-03-05 14:09:27

Docker存儲容器

2017-12-25 11:50:57

LinuxArch Linux

2010-03-04 16:28:17

Android核心代碼

2018-04-23 14:23:12

2015-07-27 16:06:16

VMware Thin虛擬化

2024-09-30 09:56:59

2024-08-30 08:30:29

CPU操作系統寄存器

2009-08-14 11:35:01

Scala Actor

2011-05-10 10:28:55

2024-11-18 17:12:18

C#編程.NET

2024-08-06 09:29:54

程序機器指令字符串

2020-12-24 11:19:55

JavaMapHashMap

2009-12-17 16:36:23

無線路由設置密碼

2018-07-13 15:36:52

2025-09-30 09:40:33

2025-02-08 08:21:48

Java排序Spring
點贊
收藏

51CTO技術棧公眾號

亚洲成人a**址| 国产成人在线一区二区| 自拍偷拍激情视频| 涩涩视频网站在线观看| 国产亲近乱来精品视频| 亚洲一区二区三区乱码aⅴ| 久久精品视频久久| 成人羞羞网站入口| 精品国产三级a在线观看| 免费无码不卡视频在线观看| 男人天堂久久久| 国产白丝网站精品污在线入口| 91精品国产91久久久久| 男人晚上看的视频| 天堂成人娱乐在线视频免费播放网站 | 国产日韩欧美一区二区| 性高潮视频在线观看| 欧美午夜不卡影院在线观看完整版免费| 亚洲精品av在线| 成人黄色一级大片| 芒果视频成人app| 一区二区三区**美女毛片| 日本在线免费观看一区| 亚洲精品国产手机| 精品一区二区三区影院在线午夜| 2021国产精品视频| 免费麻豆国产一区二区三区四区| 欧洲杯足球赛直播| 精品网站999www| 99热超碰在线| avtt久久| 欧美日韩一区在线观看| 久久国产成人精品国产成人亚洲| a免费在线观看| 中文字幕免费一区| 欧美日韩一区二区三区免费| 神马午夜在线观看| 国产ts人妖一区二区| 国产主播喷水一区二区| 中文字幕 人妻熟女| 免费在线播放第一区高清av| 性色av一区二区三区红粉影视| 成人自拍小视频| 97久久视频| 色悠悠久久88| 亚洲综合第一区| 精品视频黄色| 亚洲精品狠狠操| 毛茸茸free性熟hd| 成人看片爽爽爽| 精品国产三级a在线观看| 绯色av蜜臀vs少妇| 亚洲一区二区三区在线免费| 日韩一级片在线观看| 992tv人人草| 国产精品**亚洲精品| 欧美精品一级二级| 一级黄色片在线免费观看| 996久久国产精品线观看| 欧美精品tushy高清| 波多野结衣免费观看| 高清一区二区| 91精品在线免费视频| 免费一级欧美在线大片| 欧美日韩高清在线| 日本xxxx黄色| 色综合视频一区二区三区日韩| 欧美在线视频不卡| jizz欧美性11| 国产精品一区二区三区www| 91麻豆精品国产自产在线观看一区| 97人人爽人人| 色妞ww精品视频7777| 精品日韩欧美在线| av在线播放网址| 香蕉一区二区| 在线观看视频亚洲| 波多野结衣亚洲一区二区| 久久精品国产大片免费观看| 久久综合五月天| www.99re7.com| 久久久国产精品一区二区中文| 国产成人在线视频| 国产精品老熟女视频一区二区| 国产精品1区2区3区| 黑人另类av| 成人精品一区二区三区校园激情 | 欧美日韩一区二区免费视频| 91视频 -- 69xx| 青青草国产一区二区三区| 日韩欧美你懂的| 91精彩刺激对白露脸偷拍| 久久免费精品视频在这里| 欧美乱大交xxxxx另类电影| 国产精品久久久免费视频| 免费亚洲电影在线| 成人自拍爱视频| 国产三级视频在线| 亚洲综合激情网| 毛片av免费在线观看| 国产成人免费精品| 日韩欧美在线网站| 伊人网伊人影院| 欧美福利在线| 国产精品久久97| 黑人精品一区二区| 国产精品视频观看| 欧美日本视频在线观看| 日日夜夜精品| 欧美大片拔萝卜| 黄大色黄女片18免费| 亚洲国内欧美| 国产日韩专区在线| 精品亚洲成a人片在线观看| 一区二区三区在线观看欧美| www.欧美日本| 久久资源综合| 欧美成人激情在线| 中文字幕人妻精品一区| caoporn国产精品| japanese在线播放| 色狠狠一区二区三区| 亚洲欧美福利视频| 日本天堂网在线观看| 国产原创一区二区| 亚洲精品中字| 免费观看成人性生生活片| 亚洲第一二三四五区| 91嫩草丨国产丨精品| 日韩va亚洲va欧美va久久| 精品网站在线看| 黄视频在线免费看| 欧美一区二区三区四区在线观看 | 国产成一区二区| 免费av网站在线播放| 一区二区三区在线视频免费 | 看片网址国产福利av中文字幕| 国产一区二区成人久久免费影院| 三级三级久久三级久久18| 特黄毛片在线观看| 亚洲精品美女在线观看播放| 国产97免费视频| 国产在线不卡视频| 四虎4hu永久免费入口| 亚洲国产91视频| 色偷偷88888欧美精品久久久| www.久久久久久久| 久久久久久电影| 粗暴91大变态调教| 美女久久久久| 国产99视频精品免视看7| 天堂成人在线| 欧美午夜xxx| 中文字幕国产综合| 日韩激情视频在线观看| 日本在线观看一区二区| 国产成人亚洲一区二区三区| 日韩性生活视频| 国产女人高潮时对白| 亚洲免费成人av| 深夜视频在线观看| 伊人影院久久| 久久久com| 在线国产成人影院| 日韩一区二区三区xxxx| a在线观看免费| 亚洲成人av在线电影| 午夜av免费看| 日韩精品一二区| 中文字幕一区二区三区四区五区六区| 国产高清亚洲| 国内外成人免费激情在线视频网站 | 精品一区二区日韩| 久操手机在线视频| 麻豆一区一区三区四区| 国产精品久久久久久久美男 | 欧美激情视频在线免费观看 欧美视频免费一| 国产偷拍一区二区| 精品电影在线观看| 精品无码人妻一区二区免费蜜桃 | 国产亚洲色婷婷久久99精品91| 国产一区二区精品| 亚洲欧美丝袜| 国产劲爆久久| 国产精品成人观看视频国产奇米| 日本蜜桃在线观看| 亚洲国产精品国自产拍av秋霞| 日韩在线播放中文字幕| 亚洲素人一区二区| 国产美女视频免费观看下载软件| 可以免费看不卡的av网站| 懂色av粉嫩av蜜臀av| 牛牛影视一区二区三区免费看| 国产成人小视频在线观看| 日韩激情av| 在线播放日韩欧美| 欧美一区二区三区成人片在线| 在线看不卡av| 日韩免费一二三区| 中文字幕一区二区三区精华液| 手机免费看av片| 极品少妇xxxx精品少妇| 能在线观看的av| 中文字幕一区二区三区在线视频 | 在线观看一区二区视频| 18岁成人毛片| 国产欧美精品一区二区色综合朱莉| 日韩精品――色哟哟| 奇米亚洲午夜久久精品| 国产v片免费观看| 亚洲欧洲中文字幕| 日韩精品久久一区二区三区| 久久国产精品免费精品3p| 91免费电影网站| 色综合天天色| 5278欧美一区二区三区| 暖暖在线中文免费日本| 日韩网站在线观看| jizzjizz在线观看| 亚洲精品视频在线播放| 日本黄色不卡视频| 欧美一级片在线观看| 中文字幕在线网站| 91黄色在线观看| 天堂中文字幕在线观看| 亚洲h在线观看| 国产精品99精品无码视| 亚洲精品乱码久久久久久| 欧美88888| 欧美国产日韩亚洲一区| 成人免费毛片糖心| 久久综合视频网| 欧美深性狂猛ⅹxxx深喉| 成人免费视频国产在线观看| 国产老头和老头xxxx×| 国产在线精品视频| 一区二区免费av| 精品无人区卡一卡二卡三乱码免费卡| 亚洲狼人综合干| 日日摸夜夜添夜夜添国产精品| 国产精品97在线| 麻豆成人精品| 国产成人精品无码播放| 一本色道久久| 亚洲国产精品久久久久婷蜜芽| 国产日产高清欧美一区二区三区| 国产二区视频在线| 亚洲精选国产| 久久久久久久激情| 日韩和欧美的一区| 孩娇小videos精品| 久久99精品国产.久久久久久| 一区二区三区网址| 激情综合网av| 男生和女生一起差差差视频| 国产精品1区2区| 在线视频 日韩| 337p粉嫩大胆噜噜噜噜噜91av| av在线网站观看| 亚洲国产精品ⅴa在线观看| 少妇愉情理伦三级| 亚洲精品国产成人久久av盗摄 | 久久xxxx精品视频| 久久精品99国产| 蜜臀精品久久久久久蜜臀| 手机在线国产视频| 成人一区在线观看| 魔女鞋交玉足榨精调教| 中文字幕不卡在线观看| 私库av在线播放| 红桃视频成人在线观看| 日韩国产成人在线| 欧美顶级少妇做爰| 黄色一级a毛片| 亚洲视频777| 黄网站免费在线播放| 国内精品美女av在线播放| 日本高清不卡一区二区三区视频 | 国产免费人做人爱午夜视频| 日日摸夜夜添夜夜添精品视频 | 拔插拔插海外华人免费| 免费亚洲网站| 天堂在线精品视频| 99精品久久久久久| 污污视频网站在线免费观看| 一区二区三区四区乱视频| 日本中文在线播放| 欧美精品久久天天躁| 日本黄色一区二区三区| 一区二区欧美在线| 色网在线观看| 国产经典一区二区| 亚洲精品a区| 日韩欧美视频一区二区三区四区| 亚洲视频在线免费| 999香蕉视频| 国产精品夜夜嗨| 色噜噜在线观看| 综合色天天鬼久久鬼色| 成人在线免费看视频| 日韩一区二区三区四区五区六区| 青青久草在线| 欧美日韩国产va另类| 欧美97人人模人人爽人人喊视频| 国产日韩精品久久| 一区二区三区四区在线观看国产日韩| 97成人在线免费视频| 国产一区二区0| 亚洲久久久久久久| 亚洲成人精品一区二区| 国产精品欧美久久久久天天影视 | 美女被到爽高潮视频| 夜夜精品浪潮av一区二区三区| 午夜久久久久久久久久影院| 精品va天堂亚洲国产| 九色porny在线| 国产精品久久不能| 中文字幕精品影院| 成年人看的毛片| 国产精品小仙女| 日本成人精品视频| 欧美吞精做爰啪啪高潮| 日韩在线免费看| 久久男人av资源网站| 欧美精品三级在线| 一区二区日本伦理| 免费一区二区视频| 极品蜜桃臀肥臀-x88av| 精品久久久久久久久久久久久| 亚洲乱色熟女一区二区三区| 久久久99久久精品女同性| 国产精品第一国产精品| 视频一区三区| 日本va欧美va瓶| 日本成人免费视频| 欧美伊人久久久久久久久影院| 欧美美乳在线| 日韩av免费看网站| 免费毛片在线不卡| 欧美黄色一级片视频| 国产三级欧美三级日产三级99| 国产一级做a爱片久久毛片a| 精品国产亚洲在线| 麻豆网站免费在线观看| 精品久久久久久综合日本| 一区二区毛片| 爱爱免费小视频| 日本韩国精品在线| 成年人视频免费在线观看| 国产精品久久久久久搜索| 成人精品中文字幕| 亚洲视频一二三四| 亚洲日本青草视频在线怡红院| 亚洲在线免费观看视频| xx视频.9999.com| 亚洲欧美日本国产| 久久国产精品视频在线观看| 97久久超碰国产精品| 国产原创视频在线| 在线播放国产一区中文字幕剧情欧美| 国产精品蜜月aⅴ在线| 一区二区三区四区欧美日韩| 激情都市一区二区| 九九热精品在线观看| 日韩av在线看| 电影久久久久久| 综合网五月天| 成人免费视频app| 亚洲成人第一网站| 中文字幕日韩欧美在线| 国产精品美女久久久久人| 国产一区 在线播放| 91麻豆高清视频| 一级α片免费看刺激高潮视频| 久久成人精品一区二区三区| 99久久免费精品国产72精品九九| 夫妻免费无码v看片| 国产精品久久久久久久久久久免费看| 国产熟女精品视频| 国产69久久精品成人| 成人在线免费小视频| 久久久久无码精品| 日韩欧中文字幕| 搞黄网站在线观看| 久久免费视频1| 激情图片小说一区| 青青操免费在线视频| 精品国产区一区二区三区在线观看| av成人资源| 色乱码一区二区三区在线| 亚洲国产精品久久人人爱| 成人免费在线观看| 国产在线精品一区二区三区| 蜜臀av一级做a爰片久久| 久久国产免费观看| 视频在线一区二区| 欧美18xxxx| 搡的我好爽在线观看免费视频| 第一福利永久视频精品| av片在线观看网站|