Druid线程池中的连接什么时候会关闭?

Druid线程池帮我们实现了应用程序和数据之间的长连接管理,一个线上变更引起了我的疑问,如果我们数据库切换到备用集群,怎么变更?

数据库连接,一般都是域名连接,现在将域名和IP的绑定关系变了,更新ngix,通过域名能找到新的Ip,然后期望通过新IP连接数据库。此时应用程序中的数据库连接池中还保持老IP的连接,这样会造成新的连接走到新数据库,老的连接走到老数据库。
为了解决这个问题,我们当时的操作是手动将老的数据库kill了,使连接池中的老的连接失效重连到新的IP,这样操作,目标是实现了,但是风险很大,如果kill了老数据库后新的数据库不能用咋办?业务都停了,连接池中的老的连接失效重连,这个过渡时间能持续好久,我们业务是否能接受,这都是风险!

欢迎小伙伴们提供思路!!!

 

下面是我翻看Druid的清除连接的源码整理出来的几个点,注意看源码中的几处注释

1.DruidDataSource.shrink(boolean checkTime)方法中异步线程 DestroyTask 对无效的Collection做清除 

public void shrink(boolean checkTime) {
    final List evictList = new ArrayList();
    try {
        lock.lockInterruptibly();
    } catch (InterruptedException e) {
        return;
    }

    try {
        final int checkCount = poolingCount - minIdle;
        final long currentTimeMillis = System.currentTimeMillis();
        for (int i = 0; i < poolingCount; ++i) {
            DruidConnectionHolder connection = connections[i];

            if (checkTime) {

                ## 连接不管是否空闲,存活phyTimeoutMillis后强制回收,用于Destroy线程清理连接的时候的检测时间,如果不配置默认等于-1,也就是此处不检查
                if (phyTimeoutMillis > 0) {
                    long phyConnectTimeMillis = currentTimeMillis - connection.getTimeMillis();
                    if (phyConnectTimeMillis > phyTimeoutMillis) {
                        evictList.add(connection);
                        continue;
                    }
                }

                long idleMillis = currentTimeMillis - connection.getLastActiveTimeMillis();

                if (idleMillis < minEvictableIdleTimeMillis) {
                    break;
                }

                if (checkTime && i < checkCount) {
                    evictList.add(connection);
                } else if (idleMillis > maxEvictableIdleTimeMillis) {
                    ## 连接的最大存活时间,如果连接的最大时间大于 maxEvictableIdleTimeMillis ,则无视最小连接数强制回收
                    evictList.add(connection);
                }
            } else {
                if (i < checkCount) {
                    evictList.add(connection);
                } else {
                    break;
                }
            }
        }

        int removeCount = evictList.size();
        if (removeCount > 0) {
            System.arraycopy(connections, removeCount, connections, 0, poolingCount - removeCount);
            Arrays.fill(connections, poolingCount - removeCount, poolingCount, null);
            poolingCount -= removeCount;
        }
    } finally {
        lock.unlock();
    }

    for (DruidConnectionHolder item : evictList) {
        Connection connection = item.getConnection();
        JdbcUtils.close(connection);
        destroyCount.incrementAndGet();
    }
}

2.DruidDataSource.public int removeAbandoned() 方法中异步线程 DestroyTask 对无效的Collection做清除 

public int removeAbandoned() {
        int removeCount = 0;

        long currrentNanos = System.nanoTime();

        List abandonedList = new ArrayList();

        synchronized (activeConnections) {
            Iterator iter = activeConnections.keySet().iterator();

            for (; iter.hasNext();) {
                DruidPooledConnection pooledConnection = iter.next();

                if (pooledConnection.isRunning()) {
                    continue;
                }

                long timeMillis = (currrentNanos - pooledConnection.getConnectedTimeNano()) / (1000 * 1000);

               ## 通过datasource.getConnontion() 取得的连接必须在removeAbandonedTimeout这么多秒内调用close()要不我就弄死你.(就是conn不能超过指定的租期);removeAbandonedTimeoutMillis=默认300 * 1000 5分钟
                if (timeMillis >= removeAbandonedTimeoutMillis) {
                    iter.remove();
                    pooledConnection.setTraceEnable(false);
                    abandonedList.add(pooledConnection);
                }
            }
        }

        if (abandonedList.size() > 0) {
            for (DruidPooledConnection pooledConnection : abandonedList) {
                synchronized (pooledConnection) {
                    if (pooledConnection.isDisable()) {
                        continue;
                    }
                }

                JdbcUtils.close(pooledConnection);
                pooledConnection.abandond();
                removeAbandonedCount++;
                removeCount++;
                .....................
            }
        }

        return removeCount;
    }

3.DruidDataSource. public DruidPooledConnection getConnecltionDirect(long maxWaitMillis) :检查空闲连接是否有效,如果连接无效关闭连接,再新建一个可用连接

 if (isTestWhileIdle()) {
                    final long currentTimeMillis = System.currentTimeMillis();
                    final long lastActiveTimeMillis = poolableConnection.getConnectionHolder().getLastActiveTimeMillis();
                    final long idleMillis = currentTimeMillis - lastActiveTimeMillis;
                    long timeBetweenEvictionRunsMillis = this.getTimeBetweenEvictionRunsMillis();
                    if (timeBetweenEvictionRunsMillis <= 0) {
                        timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
                    }
                    ## 获取连接的时候,检查连接的空闲时间,如果空间时间大于配置的时间,检测连接是否还有效,如果连接无效关闭连接,无效连接关闭了后再新建一个可用连接
                    if (idleMillis >= timeBetweenEvictionRunsMillis) {
                        boolean validate = testConnectionInternal(poolableConnection.getConnection());
                        if (!validate) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("skip not validate connection.");
                            }

                            discardConnection(realConnection);
                            continue;
                        }
                    }
                }

还有其它地方会关闭连接,如果空闲连接数量大于配置的数量,这个时候需要关闭多余的连接。未完待续。。。。。。。。。。

 

当时我们的操作方案:

Druid线程池中的连接什么时候会关闭?_第1张图片

回滚方案

       

序号

步骤

操作人

时间

 

7

通过全局锁阻塞新集群数据写入,加锁成功后再进入下一步。
操作命令:
flush tables with read lock ;

DBA

10

 

8

记录新集群主库此时binlog的pos点。
操作命令:show master status\G;

DBA

3

 

9

新集群主库日志刷盘。
操作命令:flush logs;

DBA

3

 

10

解析生成新集群已执行过的dml/ddl语句(解析范围从步骤4的pos起始点开始到步骤9的pos结束点结束)
操作命令:python binlog2sql.py --flashback -h127.0.0.1 -P3306 -uxxx -p'xxx' -dxxx --start-file='mysql-bin.xxxxxx' --start-position=xxx --stop-position=xxx > exec.sql

DBA

以实际执行时间为准

 

11

将sql文件传回原集群主库并执行
操作命令:mysql -uroot -p < exec.sql;

DBA

以实际执行时间为准

 

12

域名进行回切。

DBA

以实际执行时间为准

 

13

迁移完成后验证:
验证应用连接是否正常
验证新集群数据库是否存在连接

DBA
业务运维

10分钟

 
         

注:

其中7到11步骤是为了保证新老集群数据一致性,如果对RTO比较在意,可以容忍小部分数据丢失,建议回退直接从12步开始执行

你可能感兴趣的:(#,MySQL)