1、什么是keepAlive

先简单说一下keepAlive在服务架构上的工作模式

1、高可用的解决方案通常为:冗余+故障自动发现转移,当一台节点负责接收请求时,发生故障那么整个应用就停止了,这时候会启动多台服务器有一台作为master提供服务,其余的为backup,master会定期向backup发送心跳,证明master还活着。

2、那么每台服务器的ip肯定是不同的,这时候会有一个虚拟ip在master上,client发起的请求都是请求的这个虚拟ip,master宕机后,重新发起ARP广播,刷新mac地址,并且把虚拟ip给新选举成功的服务器上,这样就做到了高可用。

keepAlive保活机制目的就来保障整个集群的高可用性

keepalive部署架构图 keepalive机制_服务器

那么,keepAlive思想在Druid连接池中是如何运用的?

2、keepAlive在Druid连接池中的作用

存活机制能够保证连接池中的连接是真实有效的连接,假如遇到特殊情况导致连接不可用时,keepAlive机制将无效连接进行驱逐。

开启keepAlive

// 一个连接在连接池中最小生存的时间

dataSurce.setMinEvictableIdleTimeMillis(60 * 1000);单位毫秒

// 开启keepAlive

dataSource.setKeepAlive(true);

当连接池中连接的空闲时间大于最小驱逐空闲时间,并且开启了keepAlive,那么就将该连接放入保活连接数组,并且对保活连接数组中的连接进行有效性检查。

先看DruidDataSource中的两个成员变量

// 存放检查需要抛弃的连接
private DruidConnectionHolder[] evictConnections;
// 用来存放需要连接检查的存活连接
private DruidConnectionHolder[] keepAliveConnections;

createAndStartdestroyThread()创建的destroytask线程

destroy守护线程无限循环destroyTask线程

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

    if (isRemoveAbandoned()) {
        removeAbandoned();
    }
}

关键方法shrink(true,keepAlive)

public void shrink(boolean checkTime, boolean keepAlive) {
        try {
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            return;
        }

        // 是否需要填充,开启keepAlive并且池中可用连接数+正在使用连接 < minIdle时,needFile=true
        boolean needFill = false;
        // 驱逐连接数量
        int evictCount = 0;
        // 存活连接数量
        int keepAliveCount = 0;
        int fatalErrorIncrement = fatalErrorCount - fatalErrorCountLastShrink;
        fatalErrorCountLastShrink = fatalErrorCount;

        try {
            // 未init直接return
            if (!inited) {
                return;
            }

            // 池中可用连接数 - 最小连接数 = 待检查的连接数量
            // 检测连接存活有效性,也是从这批连接中进行检查
            final int checkCount = poolingCount - minIdle;
            final long currentTimeMillis = System.currentTimeMillis();
            for (int i = 0; i < poolingCount; ++i) {
                // connections:可用连接的数组,从该数组中取出连接
                DruidConnectionHolder connection = connections[i];

                /*
                 * lastFatalErrorTimeMillis:最后发生错误的系统时间
                 * onFatalError:是否发生致命错误
                 *
                 * 如果发生了致命错误 并且 发生错误的时间 > 当前connection的连接时间,说明是在连接之后发生的错误
                 * 那么将这个连接放入keepAliveConnections数组中,这个数组用来保存待检查的存活连接,并退出此次循环
                 */
                if ((onFatalError || fatalErrorIncrement > 0) && (lastFatalErrorTimeMillis > connection.connectTimeMillis)) {
                    // keepAliveConnections: 保存待检查的存活连接
                    keepAliveConnections[keepAliveCount++] = connection;
                    continue;
                }

                if (checkTime) {
                    // pyhyTimeoutMillis:物理超时时间,默认为-1,可以通过setPhyTimeoutMillis(time);进行配置
                    if (phyTimeoutMillis > 0) {
                        // 当前系统时间 - 当前连接的连接时间 = 当前连接已经存活的时长
                        long phyConnectTimeMillis = currentTimeMillis - connection.connectTimeMillis;
                        // 当前连接存活时长 > 配置的物理连接超时时长,那么将此连接放入驱逐数组,并且退出此次循环
                        if (phyConnectTimeMillis > phyTimeoutMillis) {
                            evictConnections[evictCount++] = connection;
                            continue;
                        }
                    }
                    // connection.lastActiveTimeMillis:当前连接上一次使用的时间
                    // 当前时间 - 上一次使用时间 = 当前连接的空闲时间
                    long idleMillis = currentTimeMillis - connection.lastActiveTimeMillis;

                    // minEvictableIdleTimeMillis:连接保持空闲而不被驱逐的最长时间,默认30分钟
                    // keepAliveBetweenTimeMillis:保活检查时间间隔,默认2分钟
                    // 空闲时间 < 最下保持空闲时长 并且 小于保活检查时间间隔,则不进行该连接的保活检查
                    if (idleMillis < minEvictableIdleTimeMillis
                            && idleMillis < keepAliveBetweenTimeMillis
                    ) {
                        break;
                    }

                    // 空闲时间 >= 最小驱逐空闲时间
                    if (idleMillis >= minEvictableIdleTimeMillis) {
                        // final int checkCount = poolingCount - minIdle;
                        // 当前连接的索引 < 空闲连接数量
                        if (checkTime && i < checkCount) {
                            // 说明当前连接空闲时间超时了,要加入到驱逐数组中
                            evictConnections[evictCount++] = connection;
                            continue;
                        } else if (idleMillis > maxEvictableIdleTimeMillis) {
                            evictConnections[evictCount++] = connection;
                            continue;
                        }
                    }

                    // keepAliveBetweenTimeMillis:保活检查时间间隔
                    // keepAliveConnections:待检查的存活连接
                    // 开启了保活机制 并且 空闲时长 >= 保活检查时间间隔,那么将这个连接放入待检查存活连接数组
                    if (keepAlive && idleMillis >= keepAliveBetweenTimeMillis) {
                        keepAliveConnections[keepAliveCount++] = connection;
                    }
                } else {
                    // final int checkCount = poolingCount - minIdle;
                    // 只保留最小连接数 其余的都放入驱逐数组
                    if (i < checkCount) {
                        evictConnections[evictCount++] = connection;
                    } else {
                        break;
                    }
                }
            }
            // 待驱逐的连接数量 + 待保活校验的连接数量
            int removeCount = evictCount + keepAliveCount;
            if (removeCount > 0) {
                // 将connections数组中的removeCount个连接删掉
                // {obj,obj,obj,obj,obj} -> {obj,obj,obj,null,null}
                System.arraycopy(connections, removeCount, connections, 0, poolingCount - removeCount);
                Arrays.fill(connections, poolingCount - removeCount, poolingCount, null);
                poolingCount -= removeCount;
            }
            // 保活连接校验数量
            keepAliveCheckCount += keepAliveCount;

            // 开启了保活 并且 可用连接池中连接 < 配置的最小连接
            if (keepAlive && poolingCount + activeCount < minIdle) {
                // 需要向连接池中添加连接标记
                needFill = true;
            }
        } finally {
            lock.unlock();
        }

        // 驱逐连接
        if (evictCount > 0) {
            for (int i = 0; i < evictCount; ++i) {
                DruidConnectionHolder item = evictConnections[i];
                Connection connection = item.getConnection();
                JdbcUtils.close(connection);
                destroyCountUpdater.incrementAndGet(this);
            }
            Arrays.fill(evictConnections, null);
        }

        // 保活校验
        if (keepAliveCount > 0) {
            // 从后往前遍历校验
            // keep order
            for (int i = keepAliveCount - 1; i >= 0; --i) {
                DruidConnectionHolder holer = keepAliveConnections[i];
                Connection connection = holer.getConnection();
                holer.incrementKeepAliveCheckCount();

                boolean validate = false;
                try {
                    // 做一些sql检测,判断当前连接是否有效
                    this.validateConnection(connection);
                    // validate = true:连接有效,如果连接无效则直接抛异常 并 记录日志
                    validate = true;
                } catch (Throwable error) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("keepAliveErr", error);
                    }
                    // skip
                }

                boolean discard = !validate;
                if (validate) {
                    holer.lastKeepTimeMillis = System.currentTimeMillis();
                    // 若连接有效,则再次将此连接放入connections可用连接池数组中
                    boolean putOk = put(holer, 0L, true);
                    if (!putOk) {
                        discard = true;
                    }
                }

                // 若连接无效则丢弃
                if (discard) {
                    try {
                        connection.close();
                    } catch (Exception e) {
                        // skip
                    }

                    lock.lock();
                    try {
                        discardCount++;

                        // 若 正在使用的连接数量 + 可用连接 <= 最小连接数,则释放empty信号
                        if (activeCount + poolingCount <= minIdle) {
                            emptySignal();
                        }
                    } finally {
                        lock.unlock();
                    }
                }
            }
            this.getDataSourceStat().addKeepAliveCheckCount(keepAliveCount);
            Arrays.fill(keepAliveConnections, null);
        }

        if (needFill) {
            lock.lock();
            try {
                /*
                    poolingCount:池中可用连接数
                    activeCount:正在使用的连接数
                    createTaskCount:正在生成的连接数
                    
                    填充连接数以满足minIdle最小连接数
                 */
                int fillCount = minIdle - (activeCount + poolingCount + createTaskCount);
                for (int i = 0; i < fillCount; ++i) {
                    emptySignal();
                }
            } finally {
                lock.unlock();
            }
        } else if (onFatalError || fatalErrorIncrement > 0) {
            lock.lock();
            try {
                emptySignal();
            } finally {
                lock.unlock();
            }
        }
    }

特别关注keepAliveConnectionsevictConnections两个数组

keepAliveConnections

1、如何发生致命错误(onFatalError)并且发生错误时间是在当前连接创建之后,那么将此连接放入keepAliveConnections数组

if ((onFatalError || fatalErrorIncrement > 0) && (lastFatalErrorTimeMillis > connection.connectTimeMillis)) { // keepAliveConnections: 保存待检查的存活连接 keepAliveConnections[keepAliveCount++] = connection; continue; }

2、开启了keepAlive 并且 连接的空闲时间 >= 保活检查时间间隔,那么将此连接放入keepAliveConnections数组

if (keepAlive && idleMillis >= keepAliveBetweenTimeMillis) { keepAliveConnections[keepAliveCount++] = connection; }

evictConnections

1、当前连接存活时长 > 配置的物理连接时间时长,则放入evictConnections

if (phyConnectTimeMillis > phyTimeoutMillis) { evictConnections[evictCount++] = connection; continue; }

2、空闲时间 > 最小驱逐时间

// 当前连接的索引 < 空闲连接数量 if (checkTime && i < checkCount) { // 说明当前连接空闲时间超时了,要加入到驱逐数组中 evictConnections[evictCount++] = connection; continue; } else if (idleMillis > maxEvictableIdleTimeMillis) { evictConnections[evictCount++] = connection; continue; }

总结

keepAlive的作用就是让池中的连接保持一定数量 并且都是有效的,在高并发场景中尽量不去重复创建connection

在高并发场景中 启动datasource时,要对连接池进行预热 ,等请求来时减少创建连接的等待 

对于访问量小的服务来说,keepAlive反而会增加数据库服务器的压力 因为要维护多个连接进程,所以这个参数因项目实际情况来选择配置