【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  小结

本节主要是看了一下连接池的核心类,以及获取连接回收连接的过程,有理解不对的地方欢迎指正哈。

posted @ 2023-03-01 23:51  酷酷-  阅读(111)  评论(0编辑  收藏  举报