Druid源码阅读--带设计思想
一、Druid数据源连接池概念
对于连接来说,建立连接和销毁连接是非常耗时的,因此使用池化思想,将连接放入连接池中,便于复用。
1、Druid 中连接的概念
由于将连接放入了连接池,那么就存在两个维度的连接,一个是在连接池中真实的物理连接,一个是应用角度获取到的连接,即逻辑连接。物理连接的关闭,会直接使该连接不可用,而逻辑连接关闭则是将连接重新放回连接池中。因此在Druid连接池中,就有了物理连接Connection和逻辑连接DruidPooledConnection。逻辑连接和物理连接是有关联的,客户端拿到逻辑连接后,可以从逻辑连接中拿到真正的物理连接来操作数据库,其关联是通过DruidConnectionHolder进行关联的,在逻辑连接DruidPooledConnection中有DruidConnectionHolder属性,而DruidConnectionHolder中有物理连接属性Connection,因此就可以这样一层一层获取到物理连接。除了这三个类之外,还有一个PhysicalConnectionInfo类,其作用是用于在创建物理连接Connection时,存储一系列属性和监控信息,最终会将记录的信息赋值给DruidConnectionHolder。
逻辑连接DruidPooledConnection相关属性注释:
public class DruidPooledConnection extends PoolableWrapper implements javax.sql.PooledConnection, Connection {
private static final Log LOG = LogFactory.getLog(DruidPooledConnection.class);
public static final int MAX_RECORD_SQL_COUNT = 10;
protected Connection conn; // 物理连接
protected volatile DruidConnectionHolder holder; // 持有的物理连接封装类
protected TransactionInfo transactionInfo; // 事务信息
private final boolean dupCloseLogEnable;
protected volatile boolean traceEnable; // 是否开启追踪
protected volatile boolean disable; // 连接状态:是否可用
protected volatile boolean closed; // 连接状态:是否已关闭
static AtomicIntegerFieldUpdater CLOSING_UPDATER = AtomicIntegerFieldUpdater.newUpdater(DruidPooledConnection.class, "closing"); // 线程安全的更新 closing
protected final Thread ownerThread; // 拥有连接的线程
private long connectedTimeMillis; // 连接事件
private long connectedTimeNano; // 连接时间
private volatile boolean running; // 连接状态:是否正在运行
private volatile boolean abandoned; // 连接状态:已经被移除(疑似连接泄露)
protected StackTraceElement[] connectStackTrace; // 活跃连接堆栈查看
protected Throwable disableError; // 连接不可用错误堆栈信息
final ReentrantLock lock; // 锁
protected volatile int closing; // 连接关闭状态
protected FilterChain filterChain; // 过滤链
物理连接持有对象DruidConnectionHolder相关属性注释:
public final class DruidConnectionHolder {
private static final Log LOG = LogFactory.getLog(DruidConnectionHolder.class);
/**
* Oracle相关配置
*/
static volatile boolean ORACLE_SOCKET_FIELD_ERROR;
static volatile Field ORACLE_FIELD_NET;
static volatile Field ORACLE_FIELD_S_ATTS;
static volatile Field ORACLE_FIELD_NT;
static volatile Field ORACLE_FIELD_SOCKET;
public static boolean holdabilityUnsupported;
protected final DruidAbstractDataSource dataSource; // 数据源
protected final long connectionId; // 连接id
protected final Connection conn; // 物理连接
protected final List<ConnectionEventListener> connectionEventListeners = new CopyOnWriteArrayList<ConnectionEventListener>(); // 连接事件监听集合
protected final List<StatementEventListener> statementEventListeners = new CopyOnWriteArrayList<StatementEventListener>(); // statement事件监听集合
protected final long connectTimeMillis; // 连接时间毫秒
protected volatile long lastActiveTimeMillis; // 最后一次活跃时间
protected volatile long lastExecTimeMillis; // 最后一次执行时间
protected volatile long lastKeepTimeMillis; // 最后一次保活时间
protected volatile long lastValidTimeMillis; // 最后一次验证时间
protected long useCount; // 连接被使用次数
private long keepAliveCheckCount; // 连接探活次数
private long lastNotEmptyWaitNanos; // 上一次获取连接的等待时长
private final long createNanoSpan; // 创建的时间区间
protected PreparedStatementPool statementPool; // statement缓存
protected final List<Statement> statementTrace = new ArrayList<Statement>(2); // statement集合
protected final boolean defaultReadOnly; // 默认只读标识
protected final int defaultHoldability; // 默认长链接能力
protected final int defaultTransactionIsolation; // 默认事务隔离级别
protected final boolean defaultAutoCommit; // 默认事务自动提交
protected boolean underlyingReadOnly; // 磁层只读标识
protected int underlyingHoldability; //
protected int underlyingTransactionIsolation; // 底层事务隔离级别
protected boolean underlyingAutoCommit; // 底层事务自动提交
protected volatile boolean discard; // 连接状态:是否已丢弃
protected volatile boolean active; // 连接状态:是否活跃连接
protected final Map<String, Object> variables; // 局部变量
protected final Map<String, Object> globalVariables; // 全局变量
final ReentrantLock lock = new ReentrantLock();
protected String initSchema;
protected Socket socket;
物理连接Connection是JDBC下的包,不是Druid内部封装的。
2、Druid中连接池的属性
(1)存储属性
对于连接池而言,需要有个地方存储连接,因此就有连接池数组DruidConnectionHolder[] connections;针对于不同的业务场景还有一些其他的存储,例如如若要处理长时间未归还的连接,防止连接泄露,就需要有个地方存储被使用未归还的连接,因此有了Map<DruidPooledConnection, Object> activeConnections;如果需要对长时间未使用的连接进行保活,那么就需要一个存储需要探活连接的集合,因此就有了DruidConnectionHolder[] keepAliveConnections;如果要定时回收不可用的连接,那么就需要有一个保存不可用连接的数组DruidConnectionHolder[] evictConnections;
其实这些存储集合中,只有connections是必须的,而其他的,都是有特定的应用场景或者根据特定实现思路采用的,例如activeConnections,如果我们不需要检测连接泄露,就不需要这个集合,而keepAliveConnections和evictConnections是在处理时需要加锁,为了避免长时间加锁,快速放入一个集合,不影响取放连接而考虑的,再比如其他没有介绍到的一些存储集合,例如boolean[] connectionsFlag、DruidConnectionHolder[] shrinkBuffer等,都是具体在实现某一个功能时的临时存储项,具体会在下面源码解析中说明。
(2)线程属性
在Druid中主要的线程有三个,分别是创建连接线程CreateConnectionThread createConnectionThread,销毁连接线程DestroyTask destroyTask,日志处理线程LogStatsThread logStatsThread,对于这些线程的作用从名字就可以看出来。
Druid对于连接的生产和销毁有两种使用方式,一种是直接创建对应的线程进行处理,一种是使用线程池进行处理,例如CreateConnectionThread,在初始化的时候,就需要启动一个线程,用于创建线程(其实这里描述是不太准确的,具体应该是等待通知创建线程,具体会在下面源码解析上说明),这个线程可以是直接启动的,也可以是使用一个线程池启动的,这就要看怎么配置,如果是使用线程池的方式,Druid提供了ScheduledExecutorService createScheduler;对于销毁场景来说,也是提供了ScheduledExecutorService destroyScheduler,对于这些线程池,Druid也提供了对应的执行结果接收属性ScheduledFuture destroySchedulerFuture和private volatile Future createSchedulerFuture
对于销毁连接来说,还有一个DestroyConnectionThread线程,而创建连接却没有这一个,这主要是因为在创建连接时,在启动的时候需要有一个创建连接的线程,后续客户端在获取连接时如果没有可用的连接,就会再创建一个创建连接的线程,保证了不会少创建连接;而对于销毁连接来说,除了在启动的时候创建一个销毁连接线程外,其他没有场景再添加销毁连接的线程,因此就需要有一个不出出错、不会退出的线程定时创建线程,因此中间引入了DestroyConnectionThread,定时的创建销毁连接的线程。
(3)过滤链属性
Druid 提供了很多的功能,例如监控界面、防火墙、SQL监控等等,这些都是依赖于过滤器实现的,因此其属性中有List<Filter> filters = new CopyOnWriteArrayList<Filter>();
来存储过滤器集合,用于在各个环节进行过滤增强。
(4)连接属性
Druid中自然少不了一些数据源相关的属性,例如JDBCUrl、用户名、密码等等
(5)监控属性
Druid 的亮点就是监控做的非常好,对于监控,其增加了非常多的属性,用以记录整个连接池在使用过程中的各种数据。
(6)并发控制属性
连接池是一个存在并发的应用场景,例如A线程获取了连接,B线程是否会拿到同一个连接,或者A线程生产一个连接,放入连接池中,但是B线程整好从连接池中获取了一个连接,然后把该位置的连接清理掉,而实际上清理的是A线程刚创建的连接,这样就会存在并发问题。
Druid使用了ReentrantLock lock进行并发控制,防止并发问题的发生;同时对于生产连接和销毁连接来说,这是一组对应的内容,需要连接的时候才生产连接,否则就会浪费,因此两者需要相互协作才可以,因此Druid提供了Condition notEmpty和Condition empty,如果要获取连接,消费线程需要在notEmpty上等待,当生产者线程创建连接后,会在notEmpty上通知,如果要创建连接,生产者线程需要在empty上等待,当消费者线程需要连接时,会在empty上通知。
(7)类及属性
Druid 连接池的实现类是 DruidDataSource,其继承了父类DruidAbstractDataSource,在这两个类中,记录了所有上述的属性值
public class DruidDataSource extends DruidAbstractDataSource
implements DruidDataSourceMBean, ManagedDataSource, Referenceable, Closeable, Cloneable, ConnectionPoolDataSource, MBeanRegistration {
private static final Log LOG = LogFactory.getLog(DruidDataSource.class);
private static final long serialVersionUID = 1L;
// stats 用于监控的统计数据
private volatile long recycleErrorCount; // 逻辑连接回收重用次数
private volatile long discardErrorCount; // 回收异常次数
private volatile Throwable discardErrorLast; // 回收异常
private long connectCount; // 逻辑连接打开次数
private long closeCount; // 逻辑连接关闭次数
private volatile long connectErrorCount; // 逻辑连接错误次数
private long recycleCount; // 回收连接数
private long removeAbandonedCount; // 移除的疑似泄露连接数
private long notEmptyWaitCount; // 累计使用等待方式创建连接总次数
private long notEmptySignalCount; // 通知获取连接可用次数
private long notEmptyWaitNanos; // 累计使用等待方式创建连接总时长
private int keepAliveCheckCount; // KeepAlive检测次数
private int activePeak; // 活跃连接数峰值
private long activePeakTime; // 活跃连接数峰值时间
private int poolingPeak; // 池中连接数峰值
private long poolingPeakTime; // 池中连接数峰值时间
private volatile int keepAliveCheckErrorCount; //
private volatile Throwable keepAliveCheckErrorLast;
// store 用于存储相关内容
private volatile DruidConnectionHolder[] connections; // 连接池数组,存储当前连接池中所有的连接
private int poolingCount; // 池中连接数
private int activeCount; // 活跃连接数
private volatile long discardCount; // 校验失败废弃连接数
private int notEmptyWaitThreadCount; // 等待使用连接的线程数
private int notEmptyWaitThreadPeak; // 等待创建连接的峰值
// 连接数组:可用连接数组、待清理连接数组、保活连接数组、连接标记数组、临时buffer数组
private DruidConnectionHolder[] evictConnections; // 待清理数组,存储了需要回收的连接,用于线程回收
private DruidConnectionHolder[] keepAliveConnections; // 待探活的连接数组,存储了需要发送验证sql保活的连接,用于守护线程定时保持连接
private boolean[] connectionsFlag; // 连接标志数组,如果下标内容为true,说明是要探活或移除的连接,需要从连接池中移除
private volatile DruidConnectionHolder[] shrinkBuffer; // 可用连接临时复制数组
// threads 创建、销毁、日志打印等线程
private volatile ScheduledFuture<?> destroySchedulerFuture; //
private DestroyTask destroyTask; // 连接销毁线程
private volatile Future<?> createSchedulerFuture; // 创建连接任务异步处理返回值接收类
private CreateConnectionThread createConnectionThread; // 创建连接线程
private DestroyConnectionThread destroyConnectionThread; // 连接销毁线程,调用的是destroyTask
private LogStatsThread logStatsThread; // 日志处理线程
private int createTaskCount; // 创建连接线程数量
private volatile long createTaskIdSeed = 1L; //
private long[] createTasks; // 需要创建连接的任务数组
private final CountDownLatch initedLatch = new CountDownLatch(2); // 初始化线程池中使用,在初始化线程池时,需要初始化创建连接线程createConnectionThread和销毁连接线程destroyConnectionThread,这两个可以并发执行,因此使用initedLatch做异步同步转换
private volatile boolean enable = true;
private boolean resetStatEnable = true; // 是否可以重置监控信息
private volatile long resetCount; // 重置次数
private String initStackTrace; // 连接池初始化堆栈信息
private volatile boolean closing; // 连接状态:关闭中
private volatile boolean closed; // 连接状态:关闭
private long closeTimeMillis = -1L;
protected JdbcDataSourceStat dataSourceStat; // 数据源监控
private boolean useGlobalDataSourceStat; // 是否使用全局数据源
private boolean mbeanRegistered; // mbean注册标记
public static ThreadLocal<Long> waitNanosLocal = new ThreadLocal<Long>();
private boolean logDifferentThread = true;
private volatile boolean keepAlive; // 是否要保持心跳
private boolean asyncInit; // 异步创建连接标志
protected boolean killWhenSocketReadTimeout;
protected boolean checkExecuteTime; // 是否验证sql执行时间
private static List<Filter> autoFilters; // 自动装载(SPI)的过滤器集合
private boolean loadSpifilterSkip;
private volatile DataSourceDisableException disableException;
protected static final AtomicLongFieldUpdater<DruidDataSource> recycleErrorCountUpdater
= AtomicLongFieldUpdater.newUpdater(DruidDataSource.class, "recycleErrorCount");
// 逻辑连接错误次数
protected static final AtomicLongFieldUpdater<DruidDataSource> connectErrorCountUpdater
= AtomicLongFieldUpdater.newUpdater(DruidDataSource.class, "connectErrorCount");
protected static final AtomicLongFieldUpdater<DruidDataSource> resetCountUpdater
= AtomicLongFieldUpdater.newUpdater(DruidDataSource.class, "resetCount");
// 创建连接的任务id,防止并发
protected static final AtomicLongFieldUpdater<DruidDataSource> createTaskIdSeedUpdater
= AtomicLongFieldUpdater.newUpdater(DruidDataSource.class, "createTaskIdSeed");
// 回收异常次数
protected static final AtomicLongFieldUpdater<DruidDataSource> discardErrorCountUpdater
= AtomicLongFieldUpdater.newUpdater(DruidDataSource.class, "discardErrorCount");
protected static final AtomicIntegerFieldUpdater<DruidDataSource> keepAliveCheckErrorCountUpdater
= AtomicIntegerFieldUpdater.newUpdater(DruidDataSource.class, "keepAliveCheckErrorCount");
父类DruidAbstractDataSource
public abstract class DruidAbstractDataSource extends WrapperAdapter implements DruidAbstractDataSourceMBean, DataSource, DataSourceProxy, Serializable {
private static final long serialVersionUID = 1L;
private static final Log LOG = LogFactory.getLog(DruidAbstractDataSource.class);
public static final int DEFAULT_INITIAL_SIZE = 0; // 初始连接数,默认为0
public static final int DEFAULT_MAX_ACTIVE_SIZE = 8; // 最大活跃连接数默认值
public static final int DEFAULT_MAX_IDLE = 8; // 默认最大连接数
public static final int DEFAULT_MIN_IDLE = 0; // 最小连接数默认值
public static final int DEFAULT_MAX_WAIT = -1; // 默认最大等待时间
public static final String DEFAULT_VALIDATION_QUERY = null; // 默认验证sql
public static final boolean DEFAULT_TEST_ON_BORROW = false; // 获取连接时检测
public static final boolean DEFAULT_TEST_ON_RETURN = false; // 连接放回连接池时检测
public static final boolean DEFAULT_WHILE_IDLE = true; // 空闲时检测
public static final long DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS = 60 * 1000L; // 检查空闲连接的频率,默认值为1分钟
public static final long DEFAULT_TIME_BETWEEN_CONNECT_ERROR_MILLIS = 500; // 连接出错后重试时间间隔,默认 0.5秒
public static final int DEFAULT_NUM_TESTS_PER_EVICTION_RUN = 3; // 每次驱逐运行的测试数 todo
public static final int DEFAULT_TIME_CONNECT_TIMEOUT_MILLIS = 10_000; // 连接超时时间默认值
public static final int DEFAULT_TIME_SOCKET_TIMEOUT_MILLIS = 10_000; // TCP连接超时时间默认值
public static final long DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS = 1000L * 60L * 30L; // 最小可驱逐空闲时间毫秒
public static final long DEFAULT_MAX_EVICTABLE_IDLE_TIME_MILLIS = 1000L * 60L * 60L * 7; // 最大可驱逐空闲时间毫秒
public static final long DEFAULT_PHY_TIMEOUT_MILLIS = -1; // 物理超时毫秒
protected volatile boolean defaultAutoCommit = true; // 默认autocommit设置
protected volatile Boolean defaultReadOnly; // 默认只读设置
protected volatile Integer defaultTransactionIsolation; // 默认事务隔离
protected volatile String defaultCatalog; // 默认目录
protected String name;
protected volatile String username; // 数据库用户名
protected volatile String password; // 数据库密码
protected volatile String jdbcUrl; // 数据库url
protected volatile String driverClass; // 数据库驱动
protected volatile ClassLoader driverClassLoader; // 数据库驱动类加载器
protected volatile Properties connectProperties = new Properties(); // 连接配置
protected volatile PasswordCallback passwordCallback; // 密码回调(例如加密)
protected volatile NameCallback userCallback; // 用户名回调(例如加密)
protected volatile int initialSize = DEFAULT_INITIAL_SIZE; // 初始连接数,默认为0
protected volatile int maxActive = DEFAULT_MAX_ACTIVE_SIZE; // 最大活跃连接数,默认为8
protected volatile int minIdle = DEFAULT_MIN_IDLE; // 最小连接数,默认为0
protected volatile int maxIdle = DEFAULT_MAX_IDLE; // 最大连接数
protected volatile long maxWait = DEFAULT_MAX_WAIT; // 最大等待时间
protected int notFullTimeoutRetryCount; // 获取连接最大重试次数
protected volatile String validationQuery = DEFAULT_VALIDATION_QUERY; // 验证sql
protected volatile int validationQueryTimeout = -1; // 验证sql超时时间
protected volatile boolean testOnBorrow = DEFAULT_TEST_ON_BORROW; // 获取连接时检测
protected volatile boolean testOnReturn = DEFAULT_TEST_ON_RETURN; // 连接放回连接池时检测
protected volatile boolean testWhileIdle = DEFAULT_WHILE_IDLE; // 空闲时检测
protected volatile boolean poolPreparedStatements;
protected volatile boolean sharePreparedStatements;
protected volatile int maxPoolPreparedStatementPerConnectionSize = 10;
protected volatile boolean inited; // 连接池是否已经初始化
protected volatile boolean initExceptionThrow = true;
protected PrintWriter logWriter = new PrintWriter(System.out);
protected List<Filter> filters = new CopyOnWriteArrayList<Filter>(); // 过滤器集合
private boolean clearFiltersEnable = true;
protected volatile ExceptionSorter exceptionSorter; // ExceptionSorter类名
protected Driver driver; // 数据库驱动
protected volatile int connectTimeout; // 连接超时时间
protected volatile int socketTimeout; // Socket超时时间
private volatile String connectTimeoutStr;
private volatile String socketTimeoutSr;
protected volatile int queryTimeout; // 查询超时时间 seconds
protected volatile int transactionQueryTimeout; // 事务查询超时时间 seconds
// 创建物理连接总耗时
protected long createTimespan;
protected volatile int maxWaitThreadCount = -1; // 最大等待线程数
protected volatile boolean accessToUnderlyingConnectionAllowed = true; // 是否允许访问底层连接
protected volatile long timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS; // 检查空闲连接的频率,默认值为1分钟,超过该值需要验证连接是否有效
protected volatile int numTestsPerEvictionRun = DEFAULT_NUM_TESTS_PER_EVICTION_RUN; // 每次驱逐运行的测试数 todo
protected volatile long minEvictableIdleTimeMillis = DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS; // 最小可驱逐空闲时间毫秒
protected volatile long maxEvictableIdleTimeMillis = DEFAULT_MAX_EVICTABLE_IDLE_TIME_MILLIS; // 最大可驱逐空闲时间毫秒
protected volatile long keepAliveBetweenTimeMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS * 2; // 保活时间间隔
protected volatile long phyTimeoutMillis = DEFAULT_PHY_TIMEOUT_MILLIS; // 物理超时毫秒
protected volatile long phyMaxUseCount = -1; // 连接设置的最大使用次数(超过次数要回收连接)
protected volatile boolean removeAbandoned; // 是否要移除疑似泄露的连接
protected volatile long removeAbandonedTimeoutMillis = 300 * 1000; // 超时时间(一个连接超过该时间,且不运行,则可能存在连接泄露,需要自动回收)
protected volatile boolean logAbandoned; // 是否丢弃日志
protected volatile int maxOpenPreparedStatements = -1;
protected volatile List<String> connectionInitSqls;
protected volatile String dbTypeName;
protected volatile long timeBetweenConnectErrorMillis = DEFAULT_TIME_BETWEEN_CONNECT_ERROR_MILLIS; // 连接出错后重试时间间隔,默认 0.5秒
protected volatile ValidConnectionChecker validConnectionChecker; // 连接有效性检查类名
protected volatile boolean usePingMethod;
protected final Map<DruidPooledConnection, Object> activeConnections = new IdentityHashMap<DruidPooledConnection, Object>(); // 活跃连接
protected static final Object PRESENT = new Object();
protected long id; // 数据源ID
protected int connectionErrorRetryAttempts = 1; // 连接错误重试次数
protected boolean breakAfterAcquireFailure; // 失败后是否需要中断
protected long transactionThresholdMillis;
protected final java.util.Date createdTime = new java.util.Date();
protected java.util.Date initedTime; // 初始化完成时间
protected volatile long errorCount; // 错误数
protected volatile long dupCloseCount;
protected volatile long startTransactionCount;
protected volatile long commitCount; // 提交数
protected volatile long rollbackCount; // 回滚数
protected volatile long cachedPreparedStatementHitCount; // PSCache缓存命中次数
protected volatile long preparedStatementCount; // PSCache数量
protected volatile long closedPreparedStatementCount; // PSCache关闭数
protected volatile long cachedPreparedStatementCount; // PSCache缓存数量
protected volatile long cachedPreparedStatementDeleteCount; // PSCache缓存删除数
protected volatile long cachedPreparedStatementMissCount; // PSCache缓存不命中次数
private volatile FilterChainImpl filterChain;
// 错误数
static final AtomicLongFieldUpdater<DruidAbstractDataSource> errorCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "errorCount");
static final AtomicLongFieldUpdater<DruidAbstractDataSource> dupCloseCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "dupCloseCount");
// 事务启动数
static final AtomicLongFieldUpdater<DruidAbstractDataSource> startTransactionCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "startTransactionCount");
// 提交数
static final AtomicLongFieldUpdater<DruidAbstractDataSource> commitCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "commitCount");
static final AtomicLongFieldUpdater<DruidAbstractDataSource> rollbackCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "rollbackCount");
// PSCache命中次数
static final AtomicLongFieldUpdater<DruidAbstractDataSource> cachedPreparedStatementHitCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "cachedPreparedStatementHitCount");
static final AtomicLongFieldUpdater<DruidAbstractDataSource> preparedStatementCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "preparedStatementCount");
static final AtomicLongFieldUpdater<DruidAbstractDataSource> closedPreparedStatementCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "closedPreparedStatementCount");
static final AtomicLongFieldUpdater<DruidAbstractDataSource> cachedPreparedStatementCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "cachedPreparedStatementCount");
static final AtomicLongFieldUpdater<DruidAbstractDataSource> cachedPreparedStatementDeleteCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "cachedPreparedStatementDeleteCount");
// PSCache不命中次数
static final AtomicLongFieldUpdater<DruidAbstractDataSource> cachedPreparedStatementMissCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "cachedPreparedStatementMissCount");
protected static final AtomicReferenceFieldUpdater<DruidAbstractDataSource, FilterChainImpl> filterChainUpdater
= AtomicReferenceFieldUpdater.newUpdater(DruidAbstractDataSource.class, FilterChainImpl.class, "filterChain");
// 事务时间分布
protected final Histogram transactionHistogram = new Histogram(1,
10,
100,
1000,
10 * 1000,
100 * 1000);
private boolean dupCloseLogEnable;
private ObjectName objectName;
protected volatile long executeCount; // sql执行数
protected volatile long executeQueryCount; // 统计sql执行次数
protected volatile long executeUpdateCount; // 更新sql执行次数
protected volatile long executeBatchCount; // 批处理sql执行次数
/**
* sql统计次数线程安全类
*/
static final AtomicLongFieldUpdater<DruidAbstractDataSource> executeQueryCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "executeQueryCount");
static final AtomicLongFieldUpdater<DruidAbstractDataSource> executeUpdateCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "executeUpdateCount");
static final AtomicLongFieldUpdater<DruidAbstractDataSource> executeBatchCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "executeBatchCount");
static final AtomicLongFieldUpdater<DruidAbstractDataSource> executeCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "executeCount");
protected volatile Throwable createError; // 创建连接错误信息
protected volatile Throwable lastError; // 最近一次错误信息
protected volatile long lastErrorTimeMillis; // 最近一次错误时间
protected volatile Throwable lastCreateError; // 最近一次创建连接错误信息
protected volatile long lastCreateErrorTimeMillis; // 最近一次创建连接错误时间
protected volatile long lastCreateStartTimeMillis;
// 数据库类型
protected boolean isOracle;
protected boolean isMySql;
protected boolean useOracleImplicitCache = true;
protected ReentrantLock lock; // 可重入锁,用户可创建、可回收的加锁处理
protected Condition notEmpty; // 应用线程会在notEmpty上等待,
protected Condition empty; // 生产连接的线程会在empty上等待
protected ReentrantLock activeConnectionLock = new ReentrantLock(); // 活跃连接锁
protected volatile int createErrorCount; // 物理连接错误次数
protected volatile int creatingCount; // 创建物理连接数量统计
protected volatile int directCreateCount; // 直接创建数量
protected volatile long createCount; // 物理连接打开次数
protected volatile long destroyCount; // 物理关闭数量
protected volatile long createStartNanos; // 创建连接开始时间
// 物理连接错误次数
static final AtomicIntegerFieldUpdater<DruidAbstractDataSource> createErrorCountUpdater = AtomicIntegerFieldUpdater.newUpdater(DruidAbstractDataSource.class, "createErrorCount");
// 创建物理连接数量统计
static final AtomicIntegerFieldUpdater<DruidAbstractDataSource> creatingCountUpdater = AtomicIntegerFieldUpdater.newUpdater(DruidAbstractDataSource.class, "creatingCount");
// 直接创建数量
static final AtomicIntegerFieldUpdater<DruidAbstractDataSource> directCreateCountUpdater = AtomicIntegerFieldUpdater.newUpdater(DruidAbstractDataSource.class, "directCreateCount");
// 物理连接打开次数
static final AtomicLongFieldUpdater<DruidAbstractDataSource> createCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "createCount");
// 物理关闭数量
static final AtomicLongFieldUpdater<DruidAbstractDataSource> destroyCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "destroyCount"); // 线程安全的更新销毁连接数destroyCount
// 创建连接开始时间
static final AtomicLongFieldUpdater<DruidAbstractDataSource> createStartNanosUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "createStartNanos");
private Boolean useUnfairLock; // 是否使用公平锁
private boolean useLocalSessionState = true; // 是否使用本地会话状态
protected long timeBetweenLogStatsMillis; // 打印线程池配置信息时间间隔
protected DruidDataSourceStatLogger statLogger = new DruidDataSourceStatLoggerImpl();
protected boolean asyncCloseConnectionEnable; // 是否异步关闭连接
protected int maxCreateTaskCount = 3; // 最大创建任务数
protected boolean failFast; // 是否快速失败
protected volatile int failContinuous; // 是否连续失败标识
protected volatile long failContinuousTimeMillis; // 连续失败时间
protected ScheduledExecutorService destroyScheduler; // 销毁连接定时任务线程池
protected ScheduledExecutorService createScheduler; // 创建连接定时任务线程池
protected Executor netTimeoutExecutor; // 网络超时线程池
protected volatile boolean netTimeoutError; // 是否网络超时错误
// 连续失败时间,失败为当前时间,成功为0
static final AtomicLongFieldUpdater<DruidAbstractDataSource> failContinuousTimeMillisUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "failContinuousTimeMillis");
// 连续失败标志,失败为1,成功为0
static final AtomicIntegerFieldUpdater<DruidAbstractDataSource> failContinuousUpdater = AtomicIntegerFieldUpdater.newUpdater(DruidAbstractDataSource.class, "failContinuous");
protected boolean initVariants; // 使用初始化变量
protected boolean initGlobalVariants; // 是否初始化全局变量
protected volatile boolean onFatalError; // 是否致命错误
protected volatile int onFatalErrorMaxActive; // 当OnFatalError发生时最大使用连接数量,用于控制异常发生时并发执行SQL的数量,减轻数据库恢复的压力
protected volatile int fatalErrorCount; // 致命错误计数
protected volatile int fatalErrorCountLastShrink; // 致命错误计数最后一次收缩
protected volatile long lastFatalErrorTimeMillis; // 最近一次发生错误的时间
protected volatile String lastFatalErrorSql; // 最近一次致命错误的sql
protected volatile Throwable lastFatalError; // 最近一次致命错误
protected volatile Throwable keepAliveError;
3、验证接口与异常接口
Druid 提供了ValidConnectionChecker接口,对应有不同数据库的实现类,用来对于连接做有效性验证。
还以druidPooledConnection#handleException为入口,提供了ExceptionSorter接口,封装了对于异常的处理,不同的数据库有不同的实现,对于不同数据的错误状态码做处理。
4、连接池的总体流程分析
对于客户端来说,无非就是获取连接、使用连接、归还连接,对于实际的物理连接来说,无非是创建连接、使用连接、归还连接,在这两者之间,就是连接池的作用。
Druid的实现中,对于物理连接来说,创建一个物理连接,会将其放在连接池中,如果客户端获取一个连接,就从连接池中将连接拿走使用,在使用过程中,Druid会使用过滤器过滤增强,客户端使用完毕后归还连接,连接池再将该连接放回连接池中,同时连接池会定时检测连接,如果连接不满足继续使用的条件,会将物理连接销毁。
针对于上述的流程来说,对于创建物理连接来说,Druid还需要做一些额外的处理,例如验证连接有效性等(有效性检测使用了validateConnection方法),因此其提供了创建物理连接方法createPhysicalConnection;销毁物理连接不需要做额外的处理,直接关闭即可,因此其是直接使用了jdbc中Connection类的close()方法;获取一个逻辑连接场景,Druid 提供了getConnection()方法,回收逻辑连接,其提供了recycle(DruidPooledConnection pooledConnection)方法;除了这些主流程外,对应的主流;对应的主逻辑还有连接池的创建构造方法和初始化 init() 方法。其余的都是如何将这些方法串起来,以及做好相关的控制即可。
核心流程就是以上这些,下面就从源码层面,从最底层开始层层向上展开
二、连接验证与异常处理
上面提到 Druid 提供了ValidConnectionChecker接口和 ExceptionSorter接口,用于做连接验证和异常处理。
1、ValidConnectionChecker
ValidConnectionChecker提供了验证连接有效性方法isValidConnection和从Properties获取配置的方法configFromProperties,这里主要看isValidConnection方法。
public interface ValidConnectionChecker {
boolean isValidConnection(Connection c, String query, int validationQueryTimeout) throws Exception;
/**
* @param properties
* @since 0.2.21
*/
void configFromProperties(Properties properties);
}
ValidConnectionChecker针对于不同数据库有不同的实现,以mysql数据库为例,如果配置了使用 ping 的方式,则使用 ping 的方式验证连接是否有效,如果没有配置,则使用Statement执行配置的验证sql,如果在超时时间内执行成功则说明连接有效,否则无效。
public boolean isValidConnection(Connection conn,
String validateQuery,
int validationQueryTimeout) throws Exception {
if (conn.isClosed()) {
return false;
}
if (usePingMethod) {
if (conn instanceof DruidPooledConnection) {
conn = ((DruidPooledConnection) conn).getConnection();
}
if (conn instanceof ConnectionProxy) {
conn = ((ConnectionProxy) conn).getRawObject();
}
if (clazz.isAssignableFrom(conn.getClass())) {
if (validationQueryTimeout <= 0) {
validationQueryTimeout = DEFAULT_VALIDATION_QUERY_TIMEOUT;
}
try {
ping.invoke(conn, true, validationQueryTimeout * 1000);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof SQLException) {
throw (SQLException) cause;
}
throw e;
}
return true;
}
}
String query = validateQuery;
if (validateQuery == null || validateQuery.isEmpty()) {
query = DEFAULT_VALIDATION_QUERY;
}
Statement stmt = null;
ResultSet rs = null;
try {
stmt = conn.createStatement();
if (validationQueryTimeout > 0) {
stmt.setQueryTimeout(validationQueryTimeout);
}
rs = stmt.executeQuery(query);
return true;
} finally {
JdbcUtils.close(rs);
JdbcUtils.close(stmt);
}
}
2、ExceptionSorter
ExceptionSorter提供了判断是否是致命错误的方法isExceptionFatal和从Properties获取配置的方法configFromProperties,不同的数据库对应有不同的实现,以mysql数据库为例,isExceptionFatal方法是根据返回的状态码判断的,这里不做详述。
public interface ExceptionSorter {
/**
* Returns true or false whether or not the exception is fatal.
*
* @param e the exception
* @return true or false if the exception is fatal.
*/
boolean isExceptionFatal(SQLException e);
void configFromProperties(Properties properties);
}
三、创建物理连接
(一)创建连接线程
1、入口
Druid提供了createAndStartCreatorThread方法用以创建一个创建连接线程,在该方法中,判断如果不存在创建连接的线程池,则直接创建一个CreateConnectionThread线程并运行,该线程就是创建连接的线程。
protected void createAndStartCreatorThread() {
if (createScheduler == null) {
String threadName = "Druid-ConnectionPool-Create-" + System.identityHashCode(this);
createConnectionThread = new CreateConnectionThread(threadName);
createConnectionThread.start();
return;
}
initedLatch.countDown();
}
2、创建连接线程
创建连接线程使用自旋的方式创建连接,该线程是个守护线程,主要流程如下:
(1)判断是否要等待,即是否满足创建连接的条件,如果满足,则直接走创建流程,否则需要消费线程的通知;整个判断和等待要加锁。
(2)如果满足创建连接的条件,则创建物理连接,这里调用了createPhysicalConnection方法,最后返回了DruidConnectionHolder
(3)将连接放入连接池,这里调用put方法放入连接池
(4)如果创建连接失败,则更新上一次创建错误内容和上一次创建失败时间
public class CreateConnectionThread extends Thread {
public void run() {
......
for (; ; ) {
......
try {
boolean emptyWait = true;
// 判断是否要等待
if (......) {
emptyWait = false;
}
// 如果需要等待,则等待处理
if (emptyWait) {
......
}
}
......
try {
// 创建物理连接
connection = createPhysicalConnection();
}
......
// 将连接放入连接池
boolean result = put(connection);
......
}
}
}
3、判断是否要等待
以下两种情况不需要等待:
(1)如果有连接池存在创建连接错误信息、但是池中连接数为0、并且本次没有新增的丢弃连接,以上三者都满足,不需要等待;这一条主要说的是这个是之前有需要创建连接的需求,但是创建失败了,因此需要重新创建,因此不需要等待。
if (createError != null
&& poolingCount == 0
&& !discardChanged) {
emptyWait = false;
}
(2)异步初始化且创建的连接数小于初始化连接数,不需要等待;这一条主要说的是要保证创建的连接数要达到设置的最小连接数。
if (emptyWait
&& asyncInit && createCount < initialSize) {
emptyWait = false;
}
4、如果经过以上判断,需要等待,则还要经过以下判断:
(1)以下三个条件全部满足,则需要等待
a、连接数已经超过了等待使用连接的数量;这一条主要表达的是当前已有连接已经超过需要的连接
b、不需要keepAlive 或者 当前连接池中活跃连接和可用连接总和已经超过了最小连接数;这一条表达的是已有连接已经超过了最小连接,因为Druid限制只有keepAlive为true时,才保证连接池中的连接数 >= minIdle,因此带有keepAlive的判断。
c、不是连续失败;这一条表达的是本次创建连接不是本次失败的重试,而是一个全新的任务
if (emptyWait) {
// 以下条件都满足,说明当前不能创建连接,需要等待
if (poolingCount >= notEmptyWaitThreadCount // 连接数已经超过了等待使用连接的数量;这一条主要表达的是当前已有连接已经超过需要的连接
// 不需要keepAlive 或者 当前连接池中活跃连接和可用连接总和已经超过了最小连接数;这一条表达的是已有连接已经超过了最小连接
// 因为Druid限制只有keepAlive为true时,才保证连接池中的连接数 >= minIdle,因此带有keepAlive的判断
&& (!(keepAlive && activeCount + poolingCount < minIdle))
&& !isFailContinuous() // 不是连续失败;这一条表达的是本次创建连接不是本次失败的重试,而是一个全新的任务
) {
empty.await();
}
(2)活跃连接数+可用连接数已经超过了最大连接数;这一条表达的是连接数已经超过了上限,无论什么情况都需要等待。
if (activeCount + poolingCount >= maxActive) {
empty.await();
continue;
}
(二)创建物理连接
1、创建物理连接
上面提到会调用createPhysicalConnection创建物理连接,其主要流程:
(1)jdbc相关参数回调处理
(2)设置物理连接配置属性:url、用户名、密码、loginTimeout、loginTimeout、connectTimeout等
(3)更新创建物理连接的时间和数量
(4)创建真实物理连接
(5)设置事务是否自动提交、是否只读、事务隔离级别、日志目录、执行初始化sql、参数Map集合、全局参数Map集合
(6)使用validConnectionChecker或Statement验证连接是否正常
(7)更新物理连接创建时间和创建数量统计,重置连续失败标志和创建失败信息
(8)将物理连接封装为 PhysicalConnectionInfo
public PhysicalConnectionInfo createPhysicalConnection() throws SQLException {
......
// jdbc相关参数回调处理
PasswordCallback passwordCallback = getPasswordCallback();
......
// 设置物理连接配置属性:url、用户名、密码、loginTimeout、loginTimeout、connectTimeout等
Properties physicalConnectProperties = new Properties();
......
// 更新创建物理连接的时间和数量
createStartNanosUpdater.set(this, connectStartNanos);
creatingCountUpdater.incrementAndGet(this);
try {
// 创建真实物理连接
conn = createPhysicalConnection(url, physicalConnectProperties);
......
// 设置事务是否自动提交、是否只读、事务隔离级别、日志目录、执行初始化sql、参数Map集合、全局参数Map集合
initPhysicalConnection(conn, variables, globalVariables);
......
// 使用validConnectionChecker或Statement验证连接是否正常
validateConnection(conn);
validatedNanos = System.nanoTime();
// 更新物理连接创建时间和创建数量统计,重置连续失败标志和创建失败信息
setFailContinuous(false);
setCreateError(null);
......
// 将物理连接封装为 PhysicalConnectionInfo
return new PhysicalConnectionInfo(conn, connectStartNanos, connectedNanos, initedNanos, validatedNanos, variables, globalVariables);
}
2、jdbc相关参数回调处理
这里说明协一下jdbc相关参数回调处理,这里是考虑在配置数据库的url、用户名、密码等信息时使用了加密算法,传过来的值是加密后的值,直接创建连接是创建不了的,因此需要做回调进行处理。
// jdbc相关参数回调处理:这里是考虑在配置数据库的url、用户名、密码等信息时使用了加密算法,传过来的值是加密后的值,直接创建连接是创建不了的,因此需要做回调进行处理
String user;
if (getUserCallback() != null) {
user = getUserCallback().getName();
} else {
user = getUsername();
}
String password = getPassword();
PasswordCallback passwordCallback = getPasswordCallback();
if (passwordCallback != null) {
if (passwordCallback instanceof DruidPasswordCallback) {
DruidPasswordCallback druidPasswordCallback = (DruidPasswordCallback) passwordCallback;
druidPasswordCallback.setUrl(url);
druidPasswordCallback.setProperties(connectProperties);
}
char[] chars = passwordCallback.getPassword();
if (chars != null) {
password = new String(chars);
}
}
3、创建真实物理连接
其根据使用JDBC驱动根据JdbcUrl、用户名、密码等信息创建物理连接,同时更新连接创建数量。对于过滤链的使用,属于连接池增强内容,会单独分析。
public Connection createPhysicalConnection(String url, Properties info) throws SQLException {
Connection conn;
if (getProxyFilters().isEmpty()) {
conn = getDriver().connect(url, info);
} else {
FilterChainImpl filterChain = createChain();
conn = filterChain.connection_connect(info);
recycleFilterChain(filterChain);
}
createCountUpdater.incrementAndGet(this);
return conn;
}
4、将真实物理连接封装为PhysicalConnectionInfo
(1)创建物理连接:这里就是调用上面的createPhysicalConnection方法创建的
conn = createPhysicalConnection(url, physicalConnectProperties);
(2)初始化物理连接:设置事务是否自动提交、是否只读、事务隔离级别、日志目录、执行初始化sql、参数Map集合、全局参数Map集合
initPhysicalConnection(conn, variables, globalVariables);
(3)验证连接有效性:使用validConnectionChecker或Statement验证连接是否正常,这里就是调用第二步中的代码实现的
validateConnection(conn);
(4)更新物理连接创建时间和创建数量统计,重置连续失败标志和创建失败信息
setFailContinuous(false);
setCreateError(null);
(5)将物理连接封装为 PhysicalConnectionInfo,其包括了:数据源、物理连接、创建时间、参数Map、全局参数Map、连接时间、上次活跃时间、上次执行时间、底层自动提交、连接ID、底层长链接能力、默认事务隔离级别、默认事务自动提交、默认事务隔离级别、默认只读标识等信息
return new PhysicalConnectionInfo(conn, connectStartNanos, connectedNanos, initedNanos, validatedNanos, variables, globalVariables);
(三)将物理连接放入连接池
1、入口
上面提到会调用put方法放入连接池,在put方法中首先将physicalConnectionInfo中的值封装到DruidConnectionHolder中,然后将DruidConnectionHolder放入连接池,如果封装DruidConnectionHolder异常,就不能放入,这块需要考虑的是,如果在创建连接时,使用的是线程池的方式,那么需要将该任务从创建连接的线程池中移除,会调用clearCreateTask方法。
protected boolean put(PhysicalConnectionInfo physicalConnectionInfo) {
DruidConnectionHolder holder = null;
try {
holder = new DruidConnectionHolder(DruidDataSource.this, physicalConnectionInfo);
} catch (SQLException ex) {
lock.lock();
try {
if (createScheduler != null) {
clearCreateTask(physicalConnectionInfo.createTaskId);
}
} finally {
lock.unlock();
}
LOG.error("create connection holder error", ex);
return false;
}
return put(holder, physicalConnectionInfo.createTaskId, false);
}
2、清理创建任务
在封装DruidConnectionHolder对象时,会验证一些前置条件,如果不满足,就不能创建连接,如果是通过线程池创建连接,则调用clearCreateTask方法将创建任务从线程池中移除。
根据传入的创建连接任务ID,循环需要创建连接的任务数组,获取到当前任务,将其信息充值,重置信息包括:创建连接数组中任务id置位0(表示该任务已被清理)、创建任务数 -1、对创建连接任务集合缩容。
private boolean clearCreateTask(long taskId) {
if (createTasks == null) {
return false;
}
if (taskId == 0) {
return false;
}
// 循环需要创建连接的任务数组,获取到当前任务,将其信息重置,重置信息包括:
// 创建连接数组中任务id置位0,表示该任务已被清理;
// 创建任务数 -1;
// 如果没有需要创建的连接,则将创建连接的任务数组长度置位初始值8
for (int i = 0; i < createTasks.length; i++) {
if (createTasks[i] == taskId) {
createTasks[i] = 0;
createTaskCount--;
if (createTaskCount < 0) {
createTaskCount = 0;
}
if (createTaskCount == 0 && createTasks.length > 8) {
createTasks = new long[8];
}
return true;
}
}
if (LOG.isWarnEnabled()) {
LOG.warn("clear create task failed : " + taskId);
}
return false;
}
3、将连接放入连接池
真正把连接放入连接池的是put(DruidConnectionHolder holder, long createTaskId, boolean checkExists)方法,放连接和取连接是非常容易发生并发问题的场景,因此put方法使用了全程加锁的方式防止并发。
其首先判断一些条件,如果不满足条件,则不处理
(1)连接池的状态不正确,直接不处理。这里不进行后续处理,但是并不需要将该任务从连接池移除,这是因为连接池本身状态已经是关闭或关闭中了,就算有创建任务,也不会创建,这在创建物理连接的方法中有控制。
if (this.closing || this.closed) {
return false;
}
(2)连接已经达到上限(连接数大于等于最大连接数),则不处理,同时如果是使用的线程池创建创建连接任务,也是需要将该任务从连接池中移除的。
if (poolingCount >= maxActive) {
if (createScheduler != null) {
clearCreateTask(createTaskId);
}
return false;
}
(3)连接已经存在,则不需要再放入
if (checkExists) {
for (int i = 0; i < poolingCount; i++) {
if (connections[i] == holder) {
return false;
}
}
}
然后将连接放入可用连接数组connections,并更新连接数、连接数峰值和达到峰值的时间
connections[poolingCount] = holder;
incrementPoolingCount();
// 设置连接数峰值
if (poolingCount > poolingPeak) {
poolingPeak = poolingCount;
poolingPeakTime = System.currentTimeMillis();
}
最后唤醒一个等待获取连接的线程,更新连接使用次数。其实这里也有相关线程池的判断,即如果是使用线程池创建的任务,该任务已经完成,需要从线程池中移除该任务。
notEmpty.signal();
// 更新连接使用次数
notEmptySignalCount++;
// 清理待创建连接任务,
if (createScheduler != null) {
clearCreateTask(createTaskId);
if (poolingCount + createTaskCount < notEmptyWaitThreadCount //
&& activeCount + poolingCount + createTaskCount < maxActive) {
emptySignal();
}
}
4、全量代码
private boolean put(DruidConnectionHolder holder, long createTaskId, boolean checkExists) {
lock.lock();
try {
if (this.closing || this.closed) {
return false;
}
// 如果连接以达到上限,则移除该任务
if (poolingCount >= maxActive) {
if (createScheduler != null) {
clearCreateTask(createTaskId);
}
return false;
}
// 如果连接存在,则不处理
if (checkExists) {
for (int i = 0; i < poolingCount; i++) {
if (connections[i] == holder) {
return false;
}
}
}
// 将连接放入可用连接数组connections
connections[poolingCount] = holder;
incrementPoolingCount();
// 设置连接数峰值
if (poolingCount > poolingPeak) {
poolingPeak = poolingCount;
poolingPeakTime = System.currentTimeMillis();
}
// 通知等待使用连接的线程
notEmpty.signal();
// 更新连接使用次数
notEmptySignalCount++;
// 清理待创建连接任务,
if (createScheduler != null) {
clearCreateTask(createTaskId);
if (poolingCount + createTaskCount < notEmptyWaitThreadCount //
&& activeCount + poolingCount + createTaskCount < maxActive) {
emptySignal();
}
}
} finally {
lock.unlock();
}
return true;
}
(四)并发控制
在Druid的设计中,一旦设计一些公共内容的修改,例如从连接池中拿走一个连接、向连接池中放入一个连接、更新各种统计数据,都是需要加锁的,但是为了提升性能,在这些场景中并没有全程加锁,而是采用了减小锁粒度的方式,分段加锁的方式实现的。
在创建物理连接时,将加锁分成了两段,第一段是做前置判断时,为了避免其他线程的处理会变更连接池中的数据统计,因此加了锁,保证统计数据无误,第二段是将连接放入连接放入连接池中加锁,在这里可以做数据验证,同时可以保证没有并发的放入和获取锁导致的问题。
以上加锁实际上是将最耗时的创建物理连接的流程单独放在了两次加锁之间,也就是创建物理连接是没有加锁的,这样大大节省了整个流程的处理时间,只要在put方法中,加锁确保不会超过连接上线等等问题,这样就可以。
这里还有一个加锁场景,就是在创建连接失败后,重新通知应用线程获取连接时加了锁,这是因为JDK要求在Condition上等待或通知放行都是必须要加锁的,否则会抛出异常。
public class CreateConnectionThread extends Thread {
public void run() {
......
for (; ; ) {
// 前置验证加锁
try {
lock.lockInterruptibly();
} catch (InterruptedException e2) {
break;
}
// 所有前置处理
......
} catch (InterruptedException e) {
......
} finally {
lock.unlock();
}
PhysicalConnectionInfo connection = null;
try {
// 创建物理连接
connection = createPhysicalConnection();
} catch (SQLException e) {
......
if (errorCount > connectionErrorRetryAttempts && timeBetweenConnectErrorMillis > 0) {
setFailContinuous(true);
if (failFast) {
lock.lock();
try {
// 同时应用线程再次获取连接加锁
notEmpty.signalAll();
} finally {
lock.unlock();
}
}
......
}
}
......
// 将连接放入连接池
boolean result = put(connection);
......
}
}
}
private boolean put(DruidConnectionHolder holder, long createTaskId, boolean checkExists) {
// 将连接放入连接池加锁
lock.lock();
try {
......
} finally {
lock.unlock();
}
return true;
}
四、销毁物理连接
(一)入口
1、入口(定时线程池执行)
Druid提供了createAndStartDestroyThread()方法用以创建销毁连接的线程,说先会创建一个负责关闭物理连接的线程DestroyTask,如果存在销毁连接线程池,则将任务放入线程池中处理,如果没有线程池,则创建一个DestroyConnectionThread线程处理该任务。
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();
}
2、DestroyConnectionThread线程(单线程模拟定时执行)
由于销毁连接的线程除了在启动时创建外,并没有额外的地方创建,因此其需要定时执行,要么像上面采用定时任务线程池的方式,要么就是采用下面再一个单独的线程中自旋执行,在每一次执行完毕后,休眠指定时间后,继续下一次执行,从而模拟定时执行。
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;
}
}
}
}
3、真正的销毁线程DestroyTask
无论是使用定时任务线程池还是使用单线程模拟定时执行,最终执行的都是DestroyTask线程,其主要有两个流程,一个是调用shrink方法回收长时间不用的连接,防止长时间不用导致的问题,一个是调用removeAbandoned方法检测长时间不归还的连接,防止连接泄露。
public class DestroyTask implements Runnable {
public DestroyTask() {
}
@Override
public void run() {
// 回收
shrink(true, keepAlive);
// 检测连接泄露
if (isRemoveAbandoned()) {
removeAbandoned();
}
}
}
(二)连接回收
1、回收连接主流程
(1)从第一个连接开始循环整个连接池,判断每一个连接是否要回收和探活
(2)如果存在需要保活和回收的连接,则将连接数组connections中的这些连接先移除,避免获取连接的时候能拿到这些疑似有问题的连接
(3)如果存在需要回收的,就直接关闭连接,同时更新物理关闭数
(4)如果存在需要探活的,则需要探活,如果探活通过,重新放回连接池,如果探活失败,则进行回收
(5)操作完毕后,需要看是否需要补充连接
public void shrink(boolean checkTime, boolean keepAlive) {
final Lock lock = this.lock;
try {
lock.lockInterruptibly();
......
// 从第一个连接开始循环整个连接池,判断每一个连接是否要回收和探活
for (int i = 0; i < poolingCount; ++i) {
......
}
// 如果存在需要保活和回收的连接,则将连接数组connections中的这些连接先移除,避免获取连接的时候能拿到这些疑似有问题的连接
int removeCount = evictCount + keepAliveCount;
if (removeCount > 0) {
......
}
keepAliveCheckCount += keepAliveCount;
// 判断现有正常的连接数量+活跃的连接数量是否小于最小连接数,如果小于,则需要填充连接
if (keepAlive && poolingCount + activeCount < minIdle) {
needFill = true;
}
} finally {
lock.unlock();
}
// 如果存在需要回收的,就直接关闭连接,同时更新物理关闭数
if (evictCount > 0) {
......
}
// 如果存在需要探活的,则需要探活,如果探活通过,重新放回连接池,如果探活失败,则进行回收
if (keepAliveCount > 0) {
......
}
// 补充连接
if (needFill) {
......
}
}
2、判断连接是否要探活
判断是否要探活,如果需要探活,将探活连接放入探活数组DruidConnectionHolder[] keepAliveConnections中,同时将连接数组中该标志置位true,为后续将这些连接移出连接池做准备
需要探活主要有以下两种情况:
(1)如果存在致命错误且最近一次致命错误发生时间大于最近一次连接事件,则将连接放入保活数组keepAliveConnections;这一条说的是在上一次回收之后又发生了致命错误,且致命错误发生之后没有再使用过该连接
// 如果存在致命错误且最近一次致命错误发生时间大于最近一次连接事件,则将连接放入保活数组keepAliveConnections
if ((onFatalError || fatalErrorIncrement > 0) && (lastFatalErrorTimeMillis > connection.connectTimeMillis)) {
keepAliveConnections[keepAliveCount++] = connection;
connectionsFlag[i] = true;
continue;
}
(2)开启超时验证、开启keepAlive、空闲时间超过了设置的保活间隔,三者同时满足;这一条说的是设置了探活标识和探活时间周期的情况下,如果空闲时间超过了设置的保活时间,就需要探活
if (checkTime) {
......
// 如果需要保活,且连接空闲时间超过了设置的保活间隔,则将其放入保活数组keepAliveConnections
if (keepAlive && idleMillis >= keepAliveBetweenTimeMillis) {
keepAliveConnections[keepAliveCount++] = connection;
connectionsFlag[i] = true;
}
3、判断是否需要回收
判断是否需要回收,如果需要回收,则将该连接放入回收数组DruidConnectionHolder[] evictConnections中,同时将连接数组中该标志置位true,为后续将这些连接移出连接池做准备
需要回收主要有以下四种情况
(1)没有开启超时校验,直接回收指定个连接,即将其回收到最小连接数个连接,保证连接池中连接不会太多
if (checkTime) {
......
} else {
if (i < checkCount) {
evictConnections[evictCount++] = connection;
connectionsFlag[i] = true;
}
(2)开启了超时校验、设置了物理超时时间、物理连接时间(当前时间与上一次连接的时间差)超过了物理超时时间;这一条说的是需要超时校验且连接已经超过了配置的时间,也就是连接长时间没有使用,则要回收。这里的长时间要比探活时间长,也就是说如果短时间内没有使用,可以通过探活来保证其可用,但是长时间没有使用,就必须回收,这主要是为了避免连接长时间不用导致的一些问题。
if (checkTime) {
// 当前时间 - 连接时间的间隔如果 大于 物理连接超时时间,则需要回收
if (phyTimeoutMillis > 0) {
long phyConnectTimeMillis = currentTimeMillis - connection.connectTimeMillis;
if (phyConnectTimeMillis > phyTimeoutMillis) {
evictConnections[evictCount++] = connection;
connectionsFlag[i] = true;
continue;
}
}
(3)连接时间超过了设置的最大驱逐时间(这里不限制回收多少个,超了就直接回收,这是为了长时间使用导致的一些异常);这里其实和上面第二条差不多,也是一个连接到了一定要驱逐的时间后,就必须驱逐,主要是为了防止连接长时间不用导致的一些问题。
if (checkTime) {
......
// 判断闲置时间:即当前时间与最后活跃时间差,如果小于最小可驱逐空闲时间,则不需要处理
long idleMillis = currentTimeMillis - connection.lastActiveTimeMillis;
......
// 判断闲置时间:即当前时间与最后活跃时间差,如果大于等于最小可驱逐空闲时间毫秒
if (idleMillis >= minEvictableIdleTimeMillis) {
// 如果还没有达到回收上限,则将该连接放在回收数组evictConnections
if (checkTime && i < checkCount) {
evictConnections[evictCount++] = connection;
connectionsFlag[i] = true;
continue;
} else if (idleMillis > maxEvictableIdleTimeMillis) { // 如果空闲时间超过了最大可驱逐时间,也将该连接放入回收数组evictConnections
evictConnections[evictCount++] = connection;
connectionsFlag[i] = true;
continue;
}
}
(4)连接时间在设置的最大最小驱逐时间之间;这里只回收指定个连接,即回收到最小连接数个连接;这一条和第三条的区别是,如果空闲的时间在设置的驱逐最小最大时间范围内,建议驱逐,但是不绝对,因此只要保证连接池中的连接不要太多就可以(即回收到最小连接数即可),如果超过了最大驱逐时间,就必须强制回收。代码同上。
4、从连接池移除要探活和丢弃的连接
对于检测来说,是需要考虑并发问题的,因此加了锁,那么对于后续的检测和回收来说,如果放在连接池中直接一个一个进行探活和回收,那么也应该是要加锁的,但是这样就会影响性能,因此可以考虑将这些连接先从连接池中移出去,将连接的回收和探活单独处理,这样连接池就可以释放掉锁,不影响性能。但同时要保证连接池中的可用连接是连续的,因为Druid的设计是每一次从连接池的最后面获取一个连接,如果不连续会出现各种问题。
基于以上考虑,Druid使用一个临时的连接存储对象DruidConnectionHolder[] shrinkBuffer来做这件事,根据DruidConnectionHolder[] shrinkBuffer将可用的连接先放入shrinkBuffer中,然后将连接数组connections全部置位null,然后再将缓存数组shrinkBuffer中的连接copy到连接数组connections中,最后清空缓存数组shrinkBuffer。
int removeCount = evictCount + keepAliveCount;
if (removeCount > 0) {
int remaining = 0;
for (int i = 0; i < connections.length; i++) {
if (!connectionsFlag[i]) {
shrinkBuffer[remaining++] = connections[i];
}
}
Arrays.fill(connections, 0, poolingCount, null);
System.arraycopy(shrinkBuffer, 0, connections, 0, remaining);
Arrays.fill(shrinkBuffer, 0, remaining, null);
poolingCount -= removeCount;
}
5、连接回收
这里比较好理解,如果存在可回收的连接,直接将连接关闭,这里Druid封装了一个JdbcUtils,内部实际上也是使用 JDBC 的close方法。
// 连接回收
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);
}
6、连接探活
首先判断是否存在需要探活的连接,如果勋在,则从最后一个循环做探活处理,探活实际上是调用validateConnection验证连接是否可用,该方法上面已经说明。如果可用,就调用put方法将该连接重新放入连接池,该方法上面也已经说明,如果不可用,则需要回收。
回收的话和上面的回收处理基本一致,是调用JDBC的Connection的close方法关闭,同时也要关闭Socket。
最后,如果当前活跃连接数和可用连接数之和小于等于最小连接数,则通知生产者重新生产连接。
// 连接保活
if (keepAliveCount > 0) {
// keep order
for (int i = keepAliveCount - 1; i >= 0; --i) {
......
boolean validate = false;
try {
// 验证连接是否可用
this.validateConnection(connection);
validate = true;
} catch (Throwable error) {
......
}
boolean discard = !validate;
// 如果可用,则将其重新放入连接数组connections中,如果没有放进去,则需要将连接回收
if (validate) {
holder.lastKeepTimeMillis = System.currentTimeMillis();
boolean putOk = put(holder, 0L, true);
if (!putOk) {
discard = true;
}
}
// 连接回收
if (discard) {
try {
connection.close();
} catch (Exception error) {
......
}
if (holder.socket != null) {
try {
holder.socket.close();
} catch (Exception error) {
......
}
}
lock.lock();
try {
holder.discard = true;
discardCount++;
if (activeCount + poolingCount <= minIdle) {
emptySignal();
}
} finally {
lock.unlock();
}
}
}
this.getDataSourceStat().addKeepAliveCheckCount(keepAliveCount);
Arrays.fill(keepAliveConnections, null);
}
(三)检测连接泄漏
循环活跃连接集合Map<DruidPooledConnection, Object> activeConnections
获取每一个连接,如果连接是运行中的状态,则说明正常,否则要判断上一次连接时间距当前时间已经超过了设置的超时时间,如果超过,则说明该连接疑似泄露,就需要回收。这里先把该连接放入待回收的集合abandonedList中,然后将其从活跃连接集合中移除。
然后循环需要销毁连接的集合,使用JdbcUtils.close关闭连接,同时更新移除的疑似泄露连接数。这里在处理每个连接时都会加锁,防止存在并发处理同一个连接的问题。
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;
}
// 连接时间距当前时间已经超过了设置的超时时间,则需要回收(将该连接从活跃连接Map中移除,然后放入待回收连接集合abandonedList)
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;
}
(四)并发控制
1、连接回收并发的控制
在连接回收场景中,同样并没有全程加锁,而是采用了减小锁粒度的方式,分段加锁,在该流程中,主要有以下三个加锁场景:
(1)在前置判断中,将待回收连接和待探活连接移出连接池,这个就是加锁的,保证了整个过程不被打扰,不会出现并发问题
(2)探活成功后,调用put方法将连接放入连接池中也是加锁的,保证了不会出现并发问题
这样就将最耗时的连接回收和探活流程不加锁,保证了并发性能。
除了以上两个主流程加锁场景外,还有三个加锁场景:
(1)统计更新回收连接数和保活验证失败废弃连接数这两个统计数据时,其采用了两种防止并发的方式,在更新回收连接数时,使用了原子类AtomicLongFieldUpdater<DruidAbstractDataSource> destroyCountUpdater
进行累加,而在更新保活验证失败废弃连接数时,采用了加锁的方式更新。
(2)在通知连接生产者放行的场景中,也进行了加锁,这是因为JDK本身就会限制,如果不加锁会报错。
public void shrink(boolean checkTime, boolean keepAlive) {
final Lock lock = this.lock;
try {
// 前置验证加锁
lock.lockInterruptibly();
......
} finally {
lock.unlock();
}
// 连接回收
if (evictCount > 0) {
......
}
// 连接保活
if (keepAliveCount > 0) {
for (int i = keepAliveCount - 1; i >= 0; --i) {
......
if (validate) {
holder.lastKeepTimeMillis = System.currentTimeMillis();
// 放入连接池
boolean putOk = put(holder, 0L, true);
......
}
// 连接回收
if (discard) {
......
lock.lock();
try {
......
} finally {
lock.unlock();
}
}
}
......
}
// 通知加锁
if (needFill) {
lock.lock();
try {
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();
}
}
}
private boolean put(DruidConnectionHolder holder, long createTaskId, boolean checkExists) {
// 将连接放入连接池加锁
lock.lock();
try {
......
} finally {
lock.unlock();
}
return true;
}
2、检测连接泄露并发控制
检测连接泄露并发控制比较简单,主要是前置判断所有活跃连接时加锁,避免并发干扰,除了这个之外,在循环每一个需要回收的连接时会再次加锁判断该连接是否可用,如果可用就继续后续处理,如果不可用,就说明已经处理过,这里主要是避免重复关闭。
public int removeAbandoned() {
......
// 前置判断加锁
activeConnectionLock.lock();
try {
......
} 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();
}
......
}
}
五、日志线程
Druid提供了createAndLogThread()
方法来创建日志线程
private void createAndLogThread() {
if (this.timeBetweenLogStatsMillis <= 0) {
return;
}
String threadName = "Druid-ConnectionPool-Log-" + System.identityHashCode(this);
logStatsThread = new LogStatsThread(threadName);
logStatsThread.start();
this.resetStatEnable = false;
}
在该线程中,每隔一段时间调用一次logStats()
方法打印一次相关监控数据的日志。
public class LogStatsThread extends Thread {
public LogStatsThread(String name) {
super(name);
this.setDaemon(true);
}
public void run() {
try {
for (; ; ) {
try {
logStats();
} catch (Exception e) {
LOG.error("logStats error", e);
}
Thread.sleep(timeBetweenLogStatsMillis);
}
} catch (InterruptedException e) {
// skip
}
}
}
六、连接池初始化
1、连接初始化的主流程
(1)设置连接池各类参数
(2)初始化过滤器
(3)验证参数配置是否合理
(4)根据不同的数据库初始化职责类
(5)创建连接
(6)创建三大线程
(7)初始化成功的后续处理
public void init() throws SQLException {
....
// 1、设置连接池各类参数
// 2、初始化过滤器
for (Filter filter : filters) {
filter.init(this);
}
// 3、校验相关启动参数配置是否符合要求
if (maxActive <= 0) {
throw new IllegalArgumentException("illegal maxActive " + maxActive);
}
......
// 4、根据不同的数据库初始化职责类
// 4.1:根据不同的数据库初始化数据库异常类
initExceptionSorter();
// 4.2:根据不同的数据库初始化数据库验证类
initValidConnectionChecker();
// 4.3:校验验证连接设置是否正确
validationQueryCheck();
.....
// 5、创建连接
if (createScheduler != null && asyncInit) {
......
} else if (!asyncInit) {
......
}
// 6、创建三大线程
// 6.1:创建日志线程
createAndLogThread();
// 6.2:创建 创建连接线程
createAndStartCreatorThread();
// 6.3:创建 销毁连接线程
createAndStartDestroyThread();
// 7、后续处理
......
}
2、设置各类参数
(1)设置初始化堆栈信息
initStackTrace = Utils.toString(Thread.currentThread().getStackTrace());
(2)获取数据源id,同时设置connectionId、statementId、resultSetId、transactionId,保证使用连接id可以将一个执行串起来
if (this.id > 1) {
long delta = (this.id - 1) * 100000;
this.connectionIdSeedUpdater.addAndGet(this, delta);
this.statementIdSeedUpdater.addAndGet(this, delta);
this.resultSetIdSeedUpdater.addAndGet(this, delta);
this.transactionIdSeedUpdater.addAndGet(this, delta);
}
(3)从封装的驱动url进行初始化数据源的驱动、名称、链接地址、过滤器集合,从jdbcUrl和配置类中设置connectTimeout和socketTimeout
这里会调用initFromWrapDriverUrl()方法和initFromUrlOrProperties()方法,initFromWrapDriverUrl方法从封装的驱动url进行初始化数据源的驱动、名称、链接地址、过滤器集合;initFromUrlOrProperties方法从jdbcUrl和配置类中设置connectTimeout和socketTimeout。
private void initFromWrapDriverUrl() throws SQLException {
if (!jdbcUrl.startsWith(DruidDriver.DEFAULT_PREFIX)) {
return;
}
// 根据 jdbcUrl 初始化代理数据源的相关配置,以及初始化过滤器
DataSourceProxyConfig config = DruidDriver.parseConfig(jdbcUrl, null);
this.driverClass = config.getRawDriverClassName();
LOG.error("error url : '" + jdbcUrl + "', it should be : '" + config.getRawUrl() + "'");
this.jdbcUrl = config.getRawUrl();
if (this.name == null) {
this.name = config.getName();
}
// 将代理数据源配置类中的过滤器初始化,并放入数据源的的过滤器集合中
for (Filter filter : config.getFilters()) {
addFilter(filter);
}
}
private void initFromUrlOrProperties() {
if (jdbcUrl.startsWith("jdbc:mysql://") || jdbcUrl.startsWith("jdbc:mysql:loadbalance://")) {
if (jdbcUrl.indexOf("connectTimeout=") != -1 || jdbcUrl.indexOf("socketTimeout=") != -1) {
String[] items = jdbcUrl.split("(\\?|&)");
for (int i = 0; i < items.length; i++) {
String item = items[i];
if (item.startsWith("connectTimeout=")) {
String strVal = item.substring("connectTimeout=".length());
setConnectTimeout(strVal);
} else if (item.startsWith("socketTimeout=")) {
String strVal = item.substring("socketTimeout=".length());
setSocketTimeout(strVal);
}
}
}
Object propertyConnectTimeout = connectProperties.get("connectTimeout");
if (propertyConnectTimeout instanceof String) {
setConnectTimeout((String) propertyConnectTimeout);
} else if (propertyConnectTimeout instanceof Number) {
setConnectTimeout(((Number) propertyConnectTimeout).intValue());
}
Object propertySocketTimeout = connectProperties.get("socketTimeout");
if (propertySocketTimeout instanceof String) {
setSocketTimeout((String) propertySocketTimeout);
} else if (propertySocketTimeout instanceof Number) {
setSocketTimeout(((Number) propertySocketTimeout).intValue());
}
}
}
3、初始化过滤器
这个单独放在过滤器上说明。
4、验证参数配置是否合理
(1)最大连接数、最小连接数、初始化连接数
(2)日志打印间隔与全局数据源监控
(3)连接驱逐的最大时间间隔与最小时间间隔
(4)保活时间间隔与探活时间间隔
if (maxActive <= 0) {
throw new IllegalArgumentException("illegal maxActive " + maxActive);
}
if (maxActive < minIdle) {
throw new IllegalArgumentException("illegal maxActive " + maxActive);
}
if (getInitialSize() > maxActive) {
throw new IllegalArgumentException("illegal initialSize " + this.initialSize + ", maxActive " + maxActive);
}
if (timeBetweenLogStatsMillis > 0 && useGlobalDataSourceStat) {
throw new IllegalArgumentException("timeBetweenLogStatsMillis not support useGlobalDataSourceStat=true");
}
if (maxEvictableIdleTimeMillis < minEvictableIdleTimeMillis) {
throw new SQLException("maxEvictableIdleTimeMillis must be grater than minEvictableIdleTimeMillis");
}
if (keepAlive && keepAliveBetweenTimeMillis <= timeBetweenEvictionRunsMillis) {
throw new SQLException("keepAliveBetweenTimeMillis must be greater than timeBetweenEvictionRunsMillis");
}
if (this.driverClass != null) {
this.driverClass = driverClass.trim();
}
5、根据不同的数据库初始化职责类
(1)调用initCheck();
方法初始化数据库异常处理类exceptionSorter
protected void initCheck() throws SQLException {
DbType dbType = DbType.of(this.dbTypeName);
if (dbType == DbType.oracle) {
......
} else if (dbType == DbType.db2) {
db2ValidationQueryCheck();
} else if (dbType == DbType.mysql
|| JdbcUtils.MYSQL_DRIVER.equals(this.driverClass)
|| JdbcUtils.MYSQL_DRIVER_6.equals(this.driverClass)
|| JdbcUtils.MYSQL_DRIVER_603.equals(this.driverClass)
) {
isMySql = true;
}
}
(2)调用initExceptionSorter()
方法初始化数据库连接验证类validConnectionChecker
private void initExceptionSorter() {
......
for (Class<?> driverClass = driver.getClass(); ; ) {
String realDriverClassName = driverClass.getName();
if (realDriverClassName.equals(JdbcConstants.MYSQL_DRIVER) //
|| realDriverClassName.equals(JdbcConstants.MYSQL_DRIVER_6)
|| realDriverClassName.equals(JdbcConstants.MYSQL_DRIVER_603)) {
this.exceptionSorter = new MySqlExceptionSorter();
this.isMySql = true;
} else if (......
}
(3)对于验证信息的校验:调用validationQueryCheck()
方法校验连接验证相关的配置是否正确
(4)设置数据源监控:根据配置设置dataSourceStat,以及是否可以重置
if (isUseGlobalDataSourceStat()) {
dataSourceStat = JdbcDataSourceStat.getGlobal();
if (dataSourceStat == null) {
dataSourceStat = new JdbcDataSourceStat("Global", "Global", this.dbTypeName);
JdbcDataSourceStat.setGlobal(dataSourceStat);
}
if (dataSourceStat.getDbType() == null) {
dataSourceStat.setDbType(this.dbTypeName);
}
} else {
dataSourceStat = new JdbcDataSourceStat(this.name, this.jdbcUrl, this.dbTypeName, this.connectProperties);
}
// 设置数据源监控是否可重置
dataSourceStat.setResetStatEnable(this.resetStatEnable);
(5)初始化各种连接数组:初始化连接池数组、待清理的连接数组、待探活的连接数组、连接标志数组、疑似泄露的连接数组,长度均为最大活跃连接数
connections = new DruidConnectionHolder[maxActive];
evictConnections = new DruidConnectionHolder[maxActive];
keepAliveConnections = new DruidConnectionHolder[maxActive];
connectionsFlag = new boolean[maxActive];
shrinkBuffer = new DruidConnectionHolder[maxActive];
6、创建连接
如果初始化连接数不为0,则需要在初始化时时间创建指定个数的连接,根据配置,可以交由线程池创建,或由主线程创建。
无论哪种方式创建,都是创建创建连接的线程进行处理,即上面的第三点。
if (createScheduler != null && asyncInit) {
for (int i = 0; i < initialSize; ++i) {
submitCreateTask(true);
}
} else if (!asyncInit) {
// 如果不是异步创建连接,则在主线程循环创建连接
while (poolingCount < initialSize) {
try {
PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection();
DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo);
connections[poolingCount++] = holder;
} catch (SQLException ex) {
LOG.error("init datasource error, url: " + this.getUrl(), ex);
if (initExceptionThrow) {
connectError = ex;
break;
} else {
Thread.sleep(3000);
}
}
}
if (poolingCount > 0) {
poolingPeak = poolingCount;
poolingPeakTime = System.currentTimeMillis();
}
}
7、创建三大线程
(1)调用createAndLogThread()
方法创建日志线程,该方法上面已经介绍
(2)调用createAndStartCreatorThread()
创建创建连接线程,该方法上面已经介绍
(3)调用createAndStartDestroyThread()
创建销毁连接线程,该方法上面已经介绍
8、初始化成功处理
更新连接池初始化完成时间等信息
9、并发控制
init方法直接全程加锁,因为初始化肯定只会有一次,因此无需考虑多个线程协作的问题。
但是这里有一个需要考虑的点,就是如果设置了最小连接数,那么在连接池初始化时,就需要创建initialSize个物理连接,而创建物理连接又是非常耗时的,从而会导致整个系统的启动会比较耗时,为了解决这个问题,建议在设置最小连接数minIdle和初始化连接池大小initialSize时,可以设置初始化连接池大小initialSize小一点,而最小连接数minIdle比initialSize大一点,这样既可以做系统的预热,确保在第一个获取连接时不会太耗时,又可以做到快速启动,即初始化时不需要设置创建那么多的物理连接。
七、获取逻辑连接
(一)入口
Druid 提供了getConnection
方法来获取一个物理连接,主要分为两步,一个是如果连接池还没有初始化,则先初始化连接池,一个是从连接池中获取一个逻辑连接;而获取逻辑连接又分为两个逻辑,一个是存在过滤器时,需要封装过滤器,另一个是没有过滤器时直接调用getConnectionDirect
方法获取一个连接。
关于过滤器的解析后面单独讨论,不过这里先提一下,无论是走哪一种方式,最终都是调用到getConnectionDirect
方法。
public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
// 初始化连接池
init();
// 这里有两个分支,判断filters是否存在过滤器,如果存在则先执行过滤器中的内容,这采用责任链模式实现。
// 如论是否有过滤器,最终都是调用getConnectionDirect方法
final int filtersSize = filters.size();
if (filtersSize > 0) {
//创建责任链
FilterChainImpl filterChain = createChain();
try {
// 将责任链封装到数据源
return filterChain.dataSource_connect(this, maxWaitMillis);
} finally {
// 重置初始化责任链过程中属性值的变化
recycleFilterChain(filterChain);
}
} else {
//直接创建连接
return getConnectionDirect(maxWaitMillis);
}
}
(二)获取逻辑连接前置处理
1、主流程
(1)使用自旋方式,获取连接
(2)从连接池中获取一个连接
(3)验证连接有效性(使用validConnectionChecker或Statement验证)
(4)是否回收连接泄露,如果回收,则将连接放入活跃连接Map,更新连接的堆栈、连接成功时间、是否处理堆栈
(5)设置数据源事务是否自动提交
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
// 使用自旋,一直尝试获取
for (; ; ) {
// 从连接池中获取一个连接
DruidPooledConnection poolableConnection;
try {
poolableConnection = getConnectionInternal(maxWaitMillis);
}
// 验证连接有效性
if (testOnBorrow) {
......
} else {
......
}
// 回收连接
if (removeAbandoned) {
......
}
// 设置数据源事务是否自动提交
if (!this.defaultAutoCommit) {
poolableConnection.setAutoCommit(false);
}
return poolableConnection;
}
}
2、调用getConnectionInternal(maxWaitMillis)
方法从连接池中获取一个连接,这个方法是主逻辑,下面介绍
3、验证连接有效性
(1)如果开启testOnBorrow,在本次获取连接时,验证数据库验证连接有效性,如果验证失败,需要回收连接
if (testOnBorrow) {
boolean validated = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validated) {
if (LOG.isDebugEnabled()) {
LOG.debug("skip not validated connection.");
}
discardConnection(poolableConnection.holder);
continue;
}
(2)如果没有开启testOnBorrow,先验证连接状态是否为已关闭,如果是则调用discardConnection
方法关闭物理连接,该方法就是上面销毁物理连接的方法。
(3)如果没有开启testOnBorrow但是开启了testWhileIdle
这里会检查连接空闲时间是否超过配置的验证时间,如果超过则验证连接(连接空闲时间即连接最近一次活跃时间距当前时间,包括了最近一次活跃时间、执行时间、心跳时间,都算是最近的活跃);如果验证失败,则调用discardConnection
方法关闭物理连接。
if (testOnBorrow) {
......
} else {
// 检测是否关闭,如果连接已关闭,则继续for循环获取下一个连接
if (poolableConnection.conn.isClosed()) {
discardConnection(poolableConnection.holder); // 传入null,避免重复关闭
continue;
}
// 连接空闲时间如果超过配置的验证时间,则验证连接(连接空闲时间即连接最近一次活跃时间距当前时间,包括了最近一次活跃时间、执行时间、心跳时间,都算是最近的活跃)
if (testWhileIdle) {
final DruidConnectionHolder holder = poolableConnection.holder;
long currentTimeMillis = System.currentTimeMillis();
long lastActiveTimeMillis = holder.lastActiveTimeMillis;
long lastExecTimeMillis = holder.lastExecTimeMillis;
long lastKeepTimeMillis = holder.lastKeepTimeMillis;
if (checkExecuteTime
&& lastExecTimeMillis != lastActiveTimeMillis) {
lastActiveTimeMillis = lastExecTimeMillis;
}
if (lastKeepTimeMillis > lastActiveTimeMillis) {
lastActiveTimeMillis = lastKeepTimeMillis;
}
long idleMillis = currentTimeMillis - lastActiveTimeMillis;
long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;
if (timeBetweenEvictionRunsMillis <= 0) {
timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
}
if (idleMillis >= timeBetweenEvictionRunsMillis
|| idleMillis < 0 // unexcepted branch
) {
boolean validated = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validated) {
if (LOG.isDebugEnabled()) {
LOG.debug("skip not validated connection.");
}
discardConnection(poolableConnection.holder);
continue;
}
}
}
}
4、检测连接泄露
是否回收连接泄露,如果回收,则将连接放入活跃连接Map,更新连接的堆栈、连接成功时间、是否处理堆栈
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();
}
}
5、设置数据源事务是否自动提交
if (!this.defaultAutoCommit) {
poolableConnection.setAutoCommit(false);
}
(三)获取逻辑连接
上面提到调用了getConnectionInternal(long maxWait)
方法来获取一个逻辑连接
1、主流程
(1)验证连接池状态:如果连接池已经关闭或者不可用,则更新逻辑连接错误次数并抛出异常
(2)验证其他信息,如果不满足条件,则不获取连接,并做对应处理
(3)从连接池中获取一个连接
(4)如果获取的连接不为空:更新活跃连接数、活跃连接峰值;如果为空,抛出异常
(5)将连接使用次数+1,并将其封装为DruidPooledConnection返回
private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
// 首先判断连接池状态 closed 和enable状态是否正确,如果不正确则抛出异常退出
if (closed) {
......
}
if (!enable) {
......
}
for (boolean createDirect = false; ; ) {
// 直接创建连接的逻辑
if (createDirect) {
......
}
// 验证其他信息,如果不满足条件,则不获取连接,并做对应处理
lock.lockInterruptibly();
// 从连接池中获取一个连接
if (maxWait > 0) {
holder = pollLast(nanos);
} else {
holder = takeLast();
}
// 如果获取的连接不为空:更新活跃连接数、活跃连接峰值;如果为空,抛出异常
if (holder != null) {
......
}
}
......
break;
}
// 如果为空,抛出异常
if (holder == null) {
......
}
// 将连接使用次数+1,并将其封装为DruidPooledConnection返回
holder.incrementUseCount();
DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder);
return poolalbeConnection;
}
2、连接池状态校验
if (closed) {
connectErrorCountUpdater.incrementAndGet(this);
throw new DataSourceClosedException("dataSource already closed at " + new Date(closeTimeMillis));
}
if (!enable) {
connectErrorCountUpdater.incrementAndGet(this);
if (disableException != null) {
throw disableException;
}
throw new DataSourceDisableException();
}
3、验证其他信息,如果不满足条件,则不获取连接,并做对应处理
(1)活跃连接数超过最大连接数,则更新连接创建中统计、直接创建连接数统计、最后活跃时间、活跃峰值相关信息,同时关闭该连接
for (boolean createDirect = false; ; ) {
// 直接创建连接的逻辑
if (createDirect) {
createStartNanosUpdater.set(this, System.nanoTime());
if (creatingCountUpdater.compareAndSet(this, 0, 1)) {
PhysicalConnectionInfo pyConnInfo = DruidDataSource.this.createPhysicalConnection();
holder = new DruidConnectionHolder(this, pyConnInfo);
holder.lastActiveTimeMillis = System.currentTimeMillis();
creatingCountUpdater.decrementAndGet(this);
directCreateCountUpdater.incrementAndGet(this);
if (LOG.isDebugEnabled()) {
LOG.debug("conn-direct_create ");
}
boolean discard;
final Lock lock = this.lock;
lock.lock();
try {
if (activeCount < maxActive) {
activeCount++;
holder.active = true;
if (activeCount > activePeak) {
activePeak = activeCount;
activePeakTime = System.currentTimeMillis();
}
break;
} else {
discard = true;
}
} finally {
lock.unlock();
}
if (discard) {
JdbcUtils.close(pyConnInfo.getPhysicalConnection());
}
}
}
final ReentrantLock lock = this.lock;
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
connectErrorCountUpdater.incrementAndGet(this);
throw new SQLException("interrupt", e);
}
try {
if (activeCount >= maxActive) {
createDirect = false;
continue;
}
(2)等待创建连接的线程数大于设置的最大值(如果设置该值才会做判断),则更新逻辑连接错误次数,并抛出异常
if (maxWaitThreadCount > 0
&& notEmptyWaitThreadCount >= maxWaitThreadCount) {
connectErrorCountUpdater.incrementAndGet(this);
throw new SQLException("maxWaitThreadCount " + maxWaitThreadCount + ", current wait Thread count "
+ lock.getQueueLength());
}
(3)如果存在致命错误 且 设置了当OnFatalError发生时最大使用连接数量,并且活跃连接数大于等于致命错误的最大活跃数,抛出异常
if (onFatalError
&& onFatalErrorMaxActive > 0
&& activeCount >= onFatalErrorMaxActive) {
connectErrorCountUpdater.incrementAndGet(this);
StringBuilder errorMsg = new StringBuilder();
errorMsg.append("onFatalError, activeCount ")
.append(activeCount)
.append(", onFatalErrorMaxActive ")
.append(onFatalErrorMaxActive);
if (lastFatalErrorTimeMillis > 0) {
errorMsg.append(", time '")
.append(StringUtils.formatDateTime19(
lastFatalErrorTimeMillis, TimeZone.getDefault()))
.append("'");
}
if (lastFatalErrorSql != null) {
errorMsg.append(", sql \n")
.append(lastFatalErrorSql);
}
throw new SQLException(
errorMsg.toString(), lastFatalError);
}
4、从连接池中获取一个连接
这里是主流程,会根是否有线程池,决定是在线程池中执行还是在当前线程执行,如论是在线程池执行还是在当前线程执行,都是看是否要有超时的逻辑 ,如果有,则调用pollLast(nanos)方法获取连接,否则调用takeLast()方法获取连接。
if (createScheduler != null
&& poolingCount == 0
&& activeCount < maxActive
&& creatingCountUpdater.get(this) == 0
&& createScheduler instanceof ScheduledThreadPoolExecutor) {
ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) createScheduler;
if (executor.getQueue().size() > 0) {
createDirect = true;
continue;
}
}
//如果maxWait大于0,调用 pollLast(nanos),反之则调用takeLast()
//获取连接的核心逻辑
if (maxWait > 0) {
holder = pollLast(nanos);
} else {
holder = takeLast();
}
5、做获取连接后的后置处理
在获取连接后,如果连接为空,则更新相关属性值,然后抛出异常;如果不为空,更新连接使用次数,并将其封装为DruidPooledConnection返回。
(四)pollLast
该方法主流程比较简单,就是使用自旋的方式从连接池中获取一个连接,如果连接池中没有可用连接,则通知生产者创建连接,而该方法则一直阻塞等待,如果超过等待时间,则抛出异常,如果在等待时间内被放行获得连接,或者在一开始就可以从连接池中获取连接,则直接从连接池中拿一个连接返回。
1、如果连接池不存在连接
(1)通知生产者创建连接,并更新等待使用连接的线程数、等待使用连接数峰值
emptySignal(); // send signal to CreateThread create connection
if (failFast && isFailContinuous()) {
throw new DataSourceNotAvailableException(createError);
}
if (estimate <= 0) {
waitNanosLocal.set(nanos - estimate);
return null;
}
// 等待创建线连接的线程数 +1,设置等待创建连接线程数峰值
notEmptyWaitThreadCount++;
if (notEmptyWaitThreadCount > notEmptyWaitThreadPeak) {
notEmptyWaitThreadPeak = notEmptyWaitThreadCount;
}
(2)阻塞等待被通知
如果在等待时间内被放行,更新累计使用等待方式创建连接总次数、累计使用等待方式创建连接总时长,同时判断如果连接不可用,则更新逻辑连接错误次数并抛出异常,
estimate = notEmpty.awaitNanos(estimate)
2、如果连接池存在连接
这里的存在连接包括方法进来就存在连接,也包括原本连接池中没有可用连接,但是在等待时间内有了连接被唤醒,那么直接从连接池中获取一个连接返回。
3、全量代码
private DruidConnectionHolder pollLast(long nanos) throws InterruptedException, SQLException {
long estimate = nanos;
// 使用自旋的方式获取连接
for (; ; ) {
if (poolingCount == 0) {
// 通知生产者创建连接
emptySignal(); // send signal to CreateThread create connection
if (failFast && isFailContinuous()) {
throw new DataSourceNotAvailableException(createError);
}
if (estimate <= 0) {
waitNanosLocal.set(nanos - estimate);
return null;
}
// 等待创建线连接的线程数 +1,设置等待创建连接线程数峰值
notEmptyWaitThreadCount++;
if (notEmptyWaitThreadCount > notEmptyWaitThreadPeak) {
notEmptyWaitThreadPeak = notEmptyWaitThreadCount;
}
try {
long startEstimate = estimate;
// 阻塞等待可以获取连接
estimate = notEmpty.awaitNanos(estimate); // signal by
// recycle or
// creator
// 累计使用等待方式创建连接总次数 +1
notEmptyWaitCount++;
notEmptyWaitNanos += (startEstimate - estimate);
if (!enable) {
connectErrorCountUpdater.incrementAndGet(this);
if (disableException != null) {
throw disableException;
}
throw new DataSourceDisableException();
}
} catch (InterruptedException ie) {
// 如果线程被中断,通知等待创建连接的线程放行,同时将通知等待
notEmpty.signal(); // propagate to non-interrupted thread
notEmptySignalCount++;
throw ie;
} finally {
notEmptyWaitThreadCount--;
}
if (poolingCount == 0) {
if (estimate > 0) {
continue;
}
waitNanosLocal.set(nanos - estimate);
return null;
}
}
decrementPoolingCount();
DruidConnectionHolder last = connections[poolingCount];
connections[poolingCount] = null;
long waitNanos = nanos - estimate;
last.setLastNotEmptyWaitNanos(waitNanos);
return last;
}
}
(五)takeLast
takeLast和pollLast逻辑基本上一样,只是阻塞等待时没有时间限制
DruidConnectionHolder takeLast() throws InterruptedException, SQLException {
try {
while (poolingCount == 0) {
emptySignal(); // send signal to CreateThread create connection
if (failFast && isFailContinuous()) {
throw new DataSourceNotAvailableException(createError);
}
notEmptyWaitThreadCount++;
if (notEmptyWaitThreadCount > notEmptyWaitThreadPeak) {
notEmptyWaitThreadPeak = notEmptyWaitThreadCount;
}
try {
notEmpty.await(); // signal by recycle or creator
} finally {
notEmptyWaitThreadCount--;
}
notEmptyWaitCount++;
if (!enable) {
connectErrorCountUpdater.incrementAndGet(this);
if (disableException != null) {
throw disableException;
}
throw new DataSourceDisableException();
}
}
} catch (InterruptedException ie) {
notEmpty.signal(); // propagate to non-interrupted thread
notEmptySignalCount++;
throw ie;
}
decrementPoolingCount();
DruidConnectionHolder last = connections[poolingCount];
connections[poolingCount] = null;
return last;
}
(六)并发控制
获取连接的主流程主要分为四个部分,分别是从连接池中获取一个连接、验证连接是否可用、连接不可用将其回收、连接可用将其放入活跃连接Map,其中验证连接是否可用这个不需要并发控制,其他三个流程都是操作的连接池,因此是需要并发控制的。
从getConnectionDirect方法中看到,只有当连接可用并将其放入活跃连接Map时加了锁,其余的两个方法都没有在该方法中加锁,这是因为这两个方法本身就需要加锁,其在方法内部做了加锁处理,外部只需要调用即可。
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
for (; ; ) {
// 获取连接
try {
poolableConnection = getConnectionInternal(maxWaitMillis);
} catch (GetConnectionTimeoutException ex) {
......
}
// 验证连接
boolean validated = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validated) {
......
discardConnection(poolableConnection.holder);
continue;
}
// 将连接放入活跃连接Map
if (removeAbandoned) {
......
activeConnectionLock.lock();
try {
activeConnections.put(poolableConnection, PRESENT);
} finally {
activeConnectionLock.unlock();
}
}
......
return poolableConnection;
}
}
在获取连接方法中,也是分为两个部分,验证数据和从连接池拿取一个连接,其余的部分不用加锁,因此其也使用了分段锁,在验证和更新连接池数据的时候,加锁,另一个就是从连接池中获取数据的时候加锁,防止病发问题。
private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
......
for (boolean createDirect = false; ; ) {
// 直接创建连接的逻辑
if (createDirect) {
if (creatingCountUpdater.compareAndSet(this, 0, 1)) {
......
lock.lock();
try {
// 更新活跃峰值等信息
......
} finally {
lock.unlock();
}
......
}
final ReentrantLock lock = this.lock;
try {
lock.lockInterruptibly();
// 数据验证及创建连接
} finally {
lock.unlock();
}
break;
}
// 其他处理
......
return poolalbeConnection;
}
关于回收连接连接调用的discardConnection方法,会在下面说明。
八、归还逻辑连接
(一)流程
Druid提供了recycle(DruidPooledConnection pooledConnection)方法用以客户端归还逻辑连接。
1、主逻辑
(1)验证:验证获取连接的线程是否和回收连接的线程是同一个
(2)将连接从活跃连接Map中移除,并充值堆栈内容
(3)如果不是自动提交且不是只读,则回滚事务
(4)重置线程各类统计数据(如果获取连接和关闭连接是同一个线程,不加锁处理,否则需要加锁处理)
(5)如果存在特殊情况,则不需要将连接放回连接池
(6)验证连接有效性
(7)验证连接池和连接状态,判断是否要丢弃物理连接
(8)否则将连接放入连接池
protected void recycle(DruidPooledConnection pooledConnection) throws SQLException {
// 验证:验证获取连接的线程是否和回收连接的线程是同一个
boolean isSameThread = pooledConnection.ownerThread == Thread.currentThread();
......
// 将连接从活跃连接Map中移除,并充值堆栈内容
if (pooledConnection.traceEnable) {
oldInfo = activeConnections.remove(pooledConnection);
pooledConnection.traceEnable = false;
......
}
......
try {
// 如果不是自动提交且不是只读,则回滚事务
if ((!isAutoCommit) && (!isReadOnly)) {
pooledConnection.rollback();
}
// 重置线程各类统计数据(如果获取连接和关闭连接是同一个线程,不加锁处理,否则需要加锁处理)
if (!isSameThread) {
......
} else {
......
}
// 如果存在特殊情况,则不需要将连接放回连接池
if (......) {
return;
}
// 验证连接有效性
if (testOnReturn) {
......
}
// 验证连接池和连接状态,判断是否要丢弃物理连接
if (......) {
discardConnection(holder);
return;
}
lock.lock();
try {
......
// 将该连接放入连接池数组connections的最后
result = putLast(holder, currentTimeMillis);
recycleCount++;
} finally {
lock.unlock();
}
......
}
2、验证与移除活跃连接
boolean isSameThread = pooledConnection.ownerThread == Thread.currentThread();
if (logDifferentThread //
&& (!asyncCloseConnectionEnable) //
&& !isSameThread
) {
LOG.warn("get/close not same thread");
}
final Connection physicalConnection = holder.conn;
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 abandoned failed. activeConnections.size " + activeConnections.size());
}
}
}
3、回滚事务与重置线程各类统计数据
if ((!isAutoCommit) && (!isReadOnly)) {
pooledConnection.rollback();
}
// reset holder, restore default settings, clear warnings
if (!isSameThread) {
final ReentrantLock lock = pooledConnection.lock;
lock.lock();
try {
holder.reset();
} finally {
lock.unlock();
}
} else {
holder.reset();
}
4、如果存在特殊情况,则不需要将连接放回连接池
(1)设置的有链接最大使用次数,且当前连接使用次数已经超过该限制,则丢弃该物理连接;这一条是为了保证连接不能一直使用,否则会发生其他异常问题。
if (phyMaxUseCount > 0 && holder.useCount >= phyMaxUseCount) {
discardConnection(holder);
return;
}
(2)如果连接已经关闭:更新连接池活跃连接数量、关闭连接数量、代理连接活跃状态
if (physicalConnection.isClosed()) {
lock.lock();
try {
if (holder.active) {
activeCount--;
holder.active = false;
}
closeCount++;
} finally {
lock.unlock();
}
return;
}
5、验证连接有效性,如果无效,则关闭连接,同时更新相关统计数据
此时会判断是否设置了testOnReturn属性,即在归还连接时是否要验证连接可用性,如果设置,则调用testConnectionInternal方法验证连接的有效性,该方法前面已经介绍过,会使用使用validConnectionChecker或Statement验证连接。
if (testOnReturn) {
boolean validated = testConnectionInternal(holder, physicalConnection);
if (!validated) {
JdbcUtils.close(physicalConnection);
destroyCountUpdater.incrementAndGet(this);
lock.lock();
try {
if (holder.active) {
activeCount--;
holder.active = false;
}
closeCount++;
} finally {
lock.unlock();
}
return;
}
}
if (holder.initSchema != null) {
holder.conn.setSchema(holder.initSchema);
holder.initSchema = null;
}
6、验证连接池和连接状态,判断是否要丢弃物理连接
(1)如果连接池已经是不可用,需要丢弃连接
if (!enable) {
discardConnection(holder);
return;
}
(2)如果连接最近一次使用事件超过了物理连接超时时间(如果设置了物理连接的超时时间),也需要丢弃该物理连接
if (phyTimeoutMillis > 0) {
long phyConnectTimeMillis = currentTimeMillis - holder.connectTimeMillis;
if (phyConnectTimeMillis > phyTimeoutMillis) {
discardConnection(holder);
return;
}
}
7、归还连接
putLast方法将连接放入连接池的最后位置,同时更新最近活跃时间、连接数峰值、通知获取连接可用次数,最后通知消费者可以获取连接
boolean putLast(DruidConnectionHolder e, long lastActiveTimeMillis) {
if (poolingCount >= maxActive || e.discard || this.closed || this.closing) {
return false;
}
e.lastActiveTimeMillis = lastActiveTimeMillis;
connections[poolingCount] = e;
incrementPoolingCount();
if (poolingCount > poolingPeak) {
poolingPeak = poolingCount;
poolingPeakTime = lastActiveTimeMillis;
}
notEmpty.signal();
notEmptySignalCount++;
return true;
}
(二)并发控制
在回收逻辑连接时,主要逻辑就是将逻辑连接重新放回连接池,这个放的动作肯定是要加锁的,但是在放之前,要做一些判断,例如判断连接不可用,那就不需要放回连接,此时就要移除活跃连接,这个阶段是要加锁的。
另外如果回收连接连接的线程和获取连接的线程不是同一个线程,那么重置该连接相关参数时,需要加锁,防止并发。
如果当前连接已经关闭或验证当前获取的连接不可用,就需要更新连接池的相关统计信息,则也是需要加锁处理的。
最后一步则是将连接放入连接池中,这一步也是需要加锁防止并发处理的。
protected void recycle(DruidPooledConnection pooledConnection) throws SQLException {
// 移除活跃连接加锁
if (pooledConnection.traceEnable) {
activeConnectionLock.lock();
try {
// 移除连接处理
} finally {
activeConnectionLock.unlock();
}
......
}
try {
......
if (!isSameThread) {
// 不同线程回收加锁处理
lock.lock();
try {
holder.reset();
} finally {
lock.unlock();
}
} else {
holder.reset();
}
......
// 更新连接池活数据加锁
if (physicalConnection.isClosed()) {
lock.lock();
try {
// 更新连接池数据逻辑
} finally {
lock.unlock();
}
return;
}
if (testOnReturn) {
boolean validated = testConnectionInternal(holder, physicalConnection);
// 验证连接异常更新连接池数据加锁
if (!validated) {
lock.lock();
try {
// 更新连接池数据
} finally {
lock.unlock();
}
return;
}
}
if (holder.initSchema != null) {
holder.conn.setSchema(holder.initSchema);
holder.initSchema = null;
}
// 丢弃物理连接
if (!enable) {
discardConnection(holder);
return;
}
......
// 将连接放入连接池,并更新相关数据加锁
lock.lock();
try {
// 将连接放入连接池,并更新相关数据
} finally {
lock.unlock();
}
if (!result) {
JdbcUtils.close(holder.conn);
LOG.info("connection recycle failed.");
}
} catch (Throwable e) {
holder.clearStatementCache();
if (!holder.discard) {
discardConnection(holder);
holder.discard = true;
}
LOG.error("recycle error", e);
recycleErrorCountUpdater.incrementAndGet(this);
}
}
九、异常处理
(一)异常处理入口
1、连接异常处理入口
在处理连接流程中,有很多关于异常的处理,最终调用的都是handleException方法。
在该方法中,首先判断连接持有的DruidConnectionHolder是否为空,如果不为空,则调用数据库连接池的handleConnectionException方法处理异常,否则就直接抛出异常
public SQLException handleException(Throwable t, String sql) throws SQLException {
final DruidConnectionHolder holder = this.holder;
//
if (holder != null) {
DruidAbstractDataSource dataSource = holder.getDataSource();
dataSource.handleConnectionException(this, t, sql);
}
if (t instanceof SQLException) {
throw (SQLException) t;
}
throw new SQLException("Error", t);
}
2、连接池异常处理
如果连接持有的DruidConnectionHolder为空,说明已经处理或无需处理,直接返回。如果需要处理,则更新连接池中错误数、最近一次错误信息、最近一次错误时间。
如果是SQLException,主要做两个操作,一个是组装连接异常事件,通知连接事件监听器;另一个是判断当前错误是否是致命错误,如果是致命错误,则调用handleFatalError方法做处理。
如果其不是SQLException,则直接抛出异常。
public void handleConnectionException(
DruidPooledConnection pooledConnection,
Throwable t,
String sql
) throws SQLException {
final DruidConnectionHolder holder = pooledConnection.getConnectionHolder();
if (holder == null) {
return;
}
// 更新错误数、最近一次错误信息、最近一次错误时间
errorCountUpdater.incrementAndGet(this);
lastError = t;
lastErrorTimeMillis = System.currentTimeMillis();
if (t instanceof SQLException) {
SQLException sqlEx = (SQLException) t;
// broadcastConnectionError
// 组装连接异常事件,通知连接事件监听器
ConnectionEvent event = new ConnectionEvent(pooledConnection, sqlEx);
for (ConnectionEventListener eventListener : holder.getConnectionEventListeners()) {
eventListener.connectionErrorOccurred(event);
}
// exceptionSorter.isExceptionFatal
// 如果是致命错误,需要做对应处理
if (exceptionSorter != null && exceptionSorter.isExceptionFatal(sqlEx)) {
handleFatalError(pooledConnection, sqlEx, sql);
}
throw sqlEx;
} else {
throw new SQLException("Error", t);
}
}
(二)致命错误处理
在handleFatalError方法中,首先判断当前连接是否还在使用,即判断是否还在做跟踪,如果在跟踪,将连接移除活跃连接Map,同时设置不再跟踪。
// 如果该连接还在做使用跟踪(是否在使用),如果在跟踪,将连接移除活跃连接Map,同时设置不再跟踪
if (conn.isTraceEnable()) {
activeConnectionLock.lock();
try {
if (conn.isTraceEnable()) {
activeConnections.remove(conn);
conn.setTraceEnable(false);
}
} finally {
activeConnectionLock.unlock();
}
}
如果连接现在还未关闭且可用,则清理Statement缓存、关闭跟踪、清空持有的DruidConnectionHolder和事务信息、设置连接池可用标识、导致连接池不可用的错误信息。
更新连接池的最近致命错误时间、致命错误数、最近致命错误信息、最近致命错误时间。
同时判断连接池中如果上次回收之后发生的致命错误数大于onFatalErrorMaxActive,则也将致命错误标志置位true。这里主要指当OnFatalError发生时最大使用连接数量,用于控制异常发生时并发执行SQL的数量,减轻数据库恢复的压力。因此当onFatalError为true时,不一定是连接池中一定有错误,也有可能是当前有链接超过了设置的onFatalErrorMaxActive。
try {
// 如果连接现在还未关闭且可用,则清理Statement缓存、关闭跟踪、清空持有的DruidConnectionHolder和事务信息、设置连接池可用标识、导致连接池不可用的错误信息
if ((!conn.closed) && !conn.disable) {
conn.disable(error);
requireDiscard = true;
}
// 更新最近致命错误时间、致命错误数、最近致命错误信息、最近致命错误时间
lastFatalErrorTimeMillis = lastErrorTimeMillis;
fatalErrorCount++;
// 如果上次回收之后发生的致命错误数大于onFatalErrorMaxActive,则也将致命错误标志置位true
// 这里主要指当OnFatalError发生时最大使用连接数量,用于控制异常发生时并发执行SQL的数量,减轻数据库恢复的压力
if (fatalErrorCount - fatalErrorCountLastShrink > onFatalErrorMaxActive) {
onFatalError = true;
}
lastFatalError = error;
lastFatalErrorSql = sql;
} finally {
lock.unlock();
}
通知生产者创建连接
if (onFatalError && holder != null && holder.getDataSource() != null) {
ReentrantLock dataSourceLock = holder.getDataSource().lock;
dataSourceLock.lock();
try {
emptySignal();
} finally {
dataSourceLock.unlock();
}
}
如果需要丢弃连接,则关闭连接
if (requireDiscard) {
if (holder.statementTrace != null) {
holder.lock.lock();
try {
for (Statement stmt : holder.statementTrace) {
JdbcUtils.close(stmt);
}
} finally {
holder.lock.unlock();
}
}
this.discardConnection(holder);
}
(三)并发控制
在处理异常信息时,主要的加锁就是更新连接池的相关数据和当前连接的处理,为了让锁的粒度更小,因此其将锁分为了以下几段:
1、移除活跃连接
2、更新连接池相关统计数据
3、通知生产者创建连接加锁
4、如果需要关闭连接,再将其分成两个锁,首先是关闭Statement,然后是调用discardConnection更新连接池和连接的相关属性值。
protected final void handleFatalError(
DruidPooledConnection conn,
SQLException error,
String sql
) throws SQLException {
// 移除活跃连接加锁
if (conn.isTraceEnable()) {
activeConnectionLock.lock();
try {
......
} finally {
activeConnectionLock.unlock();
}
}
....
// 更新连接池相关统计信息加锁
lock.lock();
try {
......
} finally {
lock.unlock();
}
// 通知生产者创建连接加锁
if (onFatalError && holder != null && holder.getDataSource() != null) {
ReentrantLock dataSourceLock = holder.getDataSource().lock;
dataSourceLock.lock();
try {
emptySignal();
} finally {
dataSourceLock.unlock();
}
}
// 丢弃连接加锁
if (requireDiscard) {
if (holder.statementTrace != null) {
holder.lock.lock();
try {
......
} finally {
holder.lock.unlock();
}
}
this.discardConnection(holder);
}
// holder.
LOG.error("{conn-" + holder.getConnectionId() + "} discard", error);
}
public void discardConnection(DruidConnectionHolder holder) {
......
// 更新连接池信息及通知生产者生产连接
lock.lock();
try {
......
} finally {
lock.unlock();
}
}
-----------------------------------------------------------
---------------------------------------------
朦胧的夜 留笔~~