【Mybatis】【数据源】【二】Mybatis源码解析-连接池管理
1 前言
上节我们主要讲解了一些数据源的分类和概念,并简单看了三种数据源工厂的创建,那么这节我们着重看下连接池方式的管理。
2 源码分析
关于连接池的几个类我先简单说下:
- PooledDataSourceFactory 数据源工厂,用于创建数据源的
- PooledDataSource 数据源对象,用于管理连接、配置的核心
- PooledConnection 连接的代理对象类,对数据库连接的增强
- PoolState 连接池容器,存放活跃和空间连接以及连接池的动态信息
那我们先从两个辅助类开始看起。
2.1 PooledConnection
PooledConnection 内部定义了一个 Connection 类型的变量,用于指向真实的数据库连接。以及一个 Connection 的代理类,用于对部分方法调用进行拦截。也定义了一些字段,用于记录数据库连接的一些运行时状态。接下来,我们来看一下 PooledConnection 的定义。
class PooledConnection implements InvocationHandler { private static final String CLOSE = "close"; private static final Class<?>[] IFACES = new Class<?>[] { Connection.class }; private final int hashCode; private final PooledDataSource dataSource; // 连接 private final Connection realConnection; // 连接代理 private final Connection proxyConnection; // 检查时间,在创建的时候会设置当时的时间戳 private long checkoutTimestamp; // 数据库连接创建时间 private long createdTimestamp; // 数据库连接最后使用时间 private long lastUsedTimestamp; // connectionTypeCode = (url + username + password).hashCode() private int connectionTypeCode; // 表示连接是否有效 private boolean valid; public PooledConnection(Connection connection, PooledDataSource dataSource) { this.hashCode = connection.hashCode(); this.realConnection = connection; this.dataSource = dataSource; this.createdTimestamp = System.currentTimeMillis(); this.lastUsedTimestamp = System.currentTimeMillis(); this.valid = true; // JDK动态代理创建代理对象 this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); // 当连接要关闭的时候,不进行关闭,而是放进连接池 if (CLOSE.equals(methodName)) { dataSource.pushConnection(this); return null; } try { if (!Object.class.equals(method.getDeclaringClass())) { // issue #579 toString() should never fail // throw an SQLException instead of a Runtime checkConnection(); } // 执行目标方法 return method.invoke(realConnection, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } }
实现了 InvocationHandler ,其实数据库连接的代理正式这个类,内部拥有真实连接的引用,并且我们观察它的 增强方法,其实主要就是拦截 close 方法,不进行真实的关闭,而是把它放进了连接池里进行管理,下边我们讲数据源对象的时候会说到。
2.2 PoolState
这个类其实就相当于连接池的容器,我们看下它的定义:
public class PoolState { protected PooledDataSource dataSource; // 空闲连接 protected final List<PooledConnection> idleConnections = new ArrayList<>(); // 正在跑的连接 protected final List<PooledConnection> activeConnections = new ArrayList<>(); /** * 从连接池中获取连接的次数 可以看到这个不安全 * 为什么不用AtomicLong * 因为下边有锁控制着,所以这里不需要 并且获取连接是个比较活跃的东西这个容易冲突 */ protected long requestCount = 0; // 请求连接总耗时 protected long accumulatedRequestTime = 0; // 执行时间总耗时 protected long accumulatedCheckoutTime = 0; // 执行时间超时的连接数 protected long claimedOverdueConnectionCount = 0; // 超时时间累加值 protected long accumulatedCheckoutTimeOfOverdueConnections = 0; // 等待时间累加值 protected long accumulatedWaitTime = 0; // 等待次数 protected long hadToWaitCount = 0; // 无效连接数 protected long badConnectionCount = 0; public PoolState(PooledDataSource dataSource) { this.dataSource = dataSource; } }
可以看到有空闲连接集合和活跃连接集合进行管理。
2.3 PooledDataSource
我们首先看到数据源的几个默认配置:
protected int poolMaximumActiveConnections = 10; // 最大空闲连接数 protected int poolMaximumIdleConnections = 5; // 最大的检查时间 protected int poolMaximumCheckoutTime = 20000; // 最大等待时间 如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒 protected int poolTimeToWait = 20000; /** * 这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程。 如果这个线程获取到的是一个坏的连接, * 那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数 * 不应该超过 poolMaximumIdleConnections 与 poolMaximumLocalBadConnectionTolerance 之和。 默认值:3(新增于 3.4.5 */ protected int poolMaximumLocalBadConnectionTolerance = 3; protected String poolPingQuery = "NO PING QUERY SET";
那么作为连接池,我们该如何获取连接呢,我们来看一下。
2.3.1 获取连接
@Override public Connection getConnection() throws SQLException { return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection(); }
/** * 获取连接 * @param username * @param password * @return * @throws SQLException */ private PooledConnection popConnection(String username, String password) throws SQLException { boolean countedWait = false; PooledConnection conn = null; long t = System.currentTimeMillis(); int localBadConnectionCount = 0; while (conn == null) { synchronized (state) { // 判断空闲连接里有没有空闲的 if (!state.idleConnections.isEmpty()) { // 有空闲的 直接拿去第一个 // Pool has available connection conn = state.idleConnections.remove(0); if (log.isDebugEnabled()) { log.debug("Checked out connection " + conn.getRealHashCode() + " from pool."); } } else { // 没有空闲的 判断是否活跃的连接数小于最大活跃连接数 // Pool does not have available connection if (state.activeConnections.size() < poolMaximumActiveConnections) { // Can create new connection 可以创建 创建出来怎么不放入活跃连接集合里呢,会放的在下边 conn = new PooledConnection(dataSource.getConnection(), this); if (log.isDebugEnabled()) { log.debug("Created connection " + conn.getRealHashCode() + "."); } } else { /** * Cannot create new connection 不能创建新的连接 * 下边就是判断第一个也就是运行时长最长的连接,判断能不能复用 * poolMaximumCheckoutTime 超时时间限制 * 也就是说当一个活跃连接超过了这个时间的话 就可以对其进行回收 */ PooledConnection oldestActiveConnection = state.activeConnections.get(0); // 获取的就是这个连接创建时候的时间戳 long longestCheckoutTime = oldestActiveConnection.getCheckoutTime(); // 如果大于限额了就表示可以进行回收 if (longestCheckoutTime > poolMaximumCheckoutTime) { // Can claim overdue connection state.claimedOverdueConnectionCount++; state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime; state.accumulatedCheckoutTime += longestCheckoutTime; // 从活跃集合移除当前连接 state.activeConnections.remove(oldestActiveConnection); // 未开启自动提交的话 回滚事务 if (!oldestActiveConnection.getRealConnection().getAutoCommit()) { try { oldestActiveConnection.getRealConnection().rollback(); } catch (SQLException e) { log.debug("Bad connection. Could not roll back"); } } // 能复用的话给真实的连接再包装 conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this); // 复用时间信息 conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp()); conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp()); // 置为无效 oldestActiveConnection.invalidate(); if (log.isDebugEnabled()) { log.debug("Claimed overdue connection " + conn.getRealHashCode() + "."); } } else { // Must wait 这里就要进行等待了 try { if (!countedWait) { state.hadToWaitCount++; countedWait = true; } if (log.isDebugEnabled()) { log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection."); } long wt = System.currentTimeMillis(); state.wait(poolTimeToWait); state.accumulatedWaitTime += System.currentTimeMillis() - wt; } catch (InterruptedException e) { break; } } } } if (conn != null) { // 验证连接 pingConnection if (conn.isValid()) { if (!conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback(); } conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password)); conn.setCheckoutTimestamp(System.currentTimeMillis()); conn.setLastUsedTimestamp(System.currentTimeMillis()); state.activeConnections.add(conn); state.requestCount++; state.accumulatedRequestTime += System.currentTimeMillis() - t; } else { if (log.isDebugEnabled()) { log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection."); } state.badConnectionCount++; localBadConnectionCount++; conn = null; if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) { if (log.isDebugEnabled()) { log.debug("PooledDataSource: Could not get a good connection to the database."); } throw new SQLException("PooledDataSource: Could not get a good connection to the database."); } } } } } if (conn == null) { if (log.isDebugEnabled()) { log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection."); } throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection."); } return conn; }
大致的一个获取过程如下:
2.3.2 回收连接
我们在上边看 PooledConnection 也就是代理连接的对象,增强的方法里边对 close 方法进行了拦截,就会调用我们的回收连接,放进我们的连接池,进行复用。那我们来看下:
/** * 回收连接 * @param conn * @throws SQLException */ protected void pushConnection(PooledConnection conn) throws SQLException { synchronized (state) { // 从活跃列表移除当前连接 state.activeConnections.remove(conn); // 连接是否正常 if (conn.isValid()) { // 空闲数小于最大空闲数并且连接值匹配 if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCo state.accumulatedCheckoutTime += conn.getCheckoutTime(); // 回滚未提交的事务 if (!conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback(); } // 把真实的连接拿出来,包装一个新的 PooledConnection PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this); // 加入空闲连接集合 state.idleConnections.add(newConn); // 复用时间信息,因为是复用连接 newConn.setCreatedTimestamp(conn.getCreatedTimestamp()); newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp()); // 状态置为无效 conn.invalidate(); if (log.isDebugEnabled()) { log.debug("Returned connection " + newConn.getRealHashCode() + " to pool."); } // 唤醒等待连接的人 state.notifyAll(); } else { state.accumulatedCheckoutTime += conn.getCheckoutTime(); // 回滚未提交的事务 if (!conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback(); } // 关闭连接 conn.getRealConnection().close(); if (log.isDebugEnabled()) { log.debug("Closed connection " + conn.getRealHashCode() + "."); } // 连接置为无效 conn.invalidate(); } } else { // 连接不正常 打印 if (log.isDebugEnabled()) { log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection."); } state.badConnectionCount++; } } }
3 小结
本节主要是看了一下连接池的核心类,以及获取连接回收连接的过程,有理解不对的地方欢迎指正哈。