平时跟RD排查问题,经常会遇到数据库连接池相关的问题,比如获取不到连接、抛异常、长时间占用无法归还、探活、性能开销等。发现不少同学对连接池仍停留在表层的一知半解,很多配置也是相互复制的,基于此,本文整理了Druid连接池的工作原理及推荐实践,希望对大家能有所帮助。
1 前言
一个正常的SQL执行流程为:
- Connection conn = DriverManager.getConnection();
- Statement stmt = conn.createStatement();
- ResultSet rs = stmt.executeQuery(sql);
- 操作rs读取数据;
- 关闭rs、stmt、conn。
示例代码如下:
java
public static void main(String[] args){
try{
Class.forName("com.mysql.cj.jdbc.Driver"); //调用Class.forName()方法加载驱动程序
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/RUNOOB?useSSL=false", "root","*****"); //创建连接
Statement stmt = conn.createStatement(); //创建Statement对象
String sql = "select * from stu"; //要执行的SQL
ResultSet rs = stmt.executeQuery(sql);//创建数据对象
System.out.println("编号"+"\t"+"姓名"+"\t"+"年龄");
while (rs.next()){
System.out.print(rs.getInt(1) + "\t");
System.out.print(rs.getString(2) + "\t");
System.out.print(rs.getInt(3) + "\t");
System.out.println();
}
rs.close();
stmt.close();
conn.close();
}catch(Exception e){
}
}
但如果每次请求都要DriverManager.getConnection()
新建连接和关闭连接,操作较重,费时费力,也影响了业务请求。 其实Connection
对象是可以重复利用的(只要保证Connection
借出后归单一线程所有,其所创建的Statement
和ResultSet
在回收前都能关闭即可),这样Connection
被重新获取后就可以跟新建的一样,从而避免底层Socket
连接的频繁创建与关闭。数据库连接池便应运而生。
DataSource
定义了一个getConnection()
的接口,具体实现可以是直接新建,也可以是从连接池里获取。用户使用完Connection
后,要手动close()
,而这个close()
也是个逻辑语义。对于MySQL JDBC的ConnectionImpl
来说,close()
是物理关闭;而对于Druid
的DruidPooledConnection
来说,close()
就是归还。
2 Druid简介
当我们有了连接池,应用程序启动时就预先建立多个数据库连接对象,然后将连接对象保存到连接池中。当客户请求到来时,从池中取出一个连接对象为客户服务。当请求完成时,客户程序调用关闭方法,将连接对象放回池中。跟现实生活中的共享单车是不是很像~
相比之下,连接池的优点显而易见:
- 资源复用:因为数据库连接可以复用,避免了频繁创建和释放连接引起的大量性能开销,同时也增加了系统运行环境的平稳性;
- 提高性能:当业务请求时,因为数据库连接在初始化时已经被创建,可以立即使用,而不需要等待连接的建立,减少了响应时间;
- 优化资源分配:对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源;
- 连接管理:数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露。
Druid连接池内部的数据结构如下(以minIdle=5,maxActive=10为例):
- 连接池采用LRU栈式置换策略(最近归还的会被最先借出);
poolingCount
:池中可用的空闲连接;activeCount
:已经借出去的连接数。两者之和为所有连接数。此时池里有7个空闲连接,poolingCount=7;empty条件变量
:连接池有空闲连接时会等待。获取连接时如果无可用空闲连接会触发signal;notEmpty条件变量
:获取连接时如果为空会等待,归还或创建连接时会触发signal。
3 初始化流程init()
触发时机 :首次getConnection()
时或直接调用init()
。
核心流程:
- 创建
initialSize
个连接; - 启动
LogStatsThread
、CreateConnectionThread
、DestroyConnectionThread
三个线程。
3.1 LogStatsThread线程(Druid-ConnectionPool-Log-)
如果timeBetweenLogStatsMillis
> 0,则每隔timeBetweenLogStatsMillis
打印一次stat日志。
3.2 CreateConnectionThread线程(Druid-ConnectionPool-Create-)
后台负责创建连接的线程。监听empty条件信号,收到信号后,如果满足条件则创建一个新连接;如果不满足,则忽略此次信号。
3.2.1 创建新连接的条件
java
// 防止创建超过maxActive数量的连接
if (activeCount + poolingCount >= maxActive) { //如果不满足条件,则忽略此次信号并继续await()
empty.await();
continue; //再次收到empty条件信号后,重新回到for (;;)处
}
3.2.2 createPhysicalConnection()创建连接流程
java
//创建一条连接,并初始化
public PhysicalConnectionInfo createPhysicalConnection() throws SQLException {
//此处省略一万行
try {
Connection conn = createPhysicalConnection(url, physicalConnectProperties); //创建一条物理连接
connectedNanos = System.nanoTime();
if (conn == null) {
throw new SQLException("connect error, url " + url + ", driverClass " + this.driverClass);
}
initPhysicalConnection(conn, variables, globalVariables); //初始化连接
initedNanos = System.nanoTime();
validateConnection(conn); //检测一下
validatedNanos = System.nanoTime();
setFailContinuous(false);
setCreateError(null);
} //此处省略一万行
return new PhysicalConnectionInfo(conn, connectStartNanos, connectedNanos, initedNanos, validatedNanos, variables, globalVariables);
}
接下来再来看createPhysicalConnection(url, info)函数,它就是负责创建一条java.sql.Connection连接,如下:
java
public Connection createPhysicalConnection(String url, Properties info) throws SQLException {
Connection conn;
if (getProxyFilters().size() == 0) {
conn = getDriver().connect(url, info); //创建一条连接
} else {
conn = new FilterChainImpl(this).connection_connect(info);
}
return conn;
}
3.2.3 put(holder, createTaskId)将连接放入连接池
将连接放入连接池尾部,并发送notEmpty条件信号。
java
//将连接放入连接池尾部,并发送notEmpty条件信号
private boolean put(DruidConnectionHolder holder, long createTaskId) {
lock.lock();
try {
if (poolingCount >= maxActive) {
return false;
}
connections[poolingCount] = holder; //放入连接池尾部
incrementPoolingCount(); //可用连接数poolingCount+1
notEmpty.signal(); //发送notEmpty条件信号
notEmptySignalCount++;
} finally {
lock.unlock();
}
return true;
}
3.3 DestroyConnectionThread线程(Druid-ConnectionPool-Destroy-)
定时扫描连接池进行探测和销毁。DestroyConnectionThread
每隔timeBetweenEvictionRunsMillis
扫描一次连接池中的空闲连接:
- 如果物理存活时间超过
phyTimeoutMillis
,则直接销毁; - 如果(
keepAlive
&& 空闲时间 >=keepAliveBetweenTimeMillis
),则进行探测; - 如果空闲时间 >=
minEvictableIdleTimeMillis
,则销毁(但要保证留下minIdle
个);而如果空闲时间超过maxEvictableIdleTimeMillis
则必须进行销毁; - 如果(
removeAbandoned
== true && 连接借出时间 >removeAbandonedTimeoutMillis
),则强制关闭其statement
并归还。
java
public void run() {
shrink(true, keepAlive); //探测与销毁
if (isRemoveAbandoned()) { //检查连接泄漏
removeAbandoned();
}
}
3.3.1 shrink(checkTime, keepAlive)流程
java
public void shrink(boolean checkTime, boolean keepAlive) {
try {
final int checkCount = poolingCount - minIdle;
final long currentTimeMillis = System.currentTimeMillis();
for (int i = 0; i < poolingCount; ++i) {
DruidConnectionHolder connection = connections[i];
if (phyTimeoutMillis > 0) { //如果连接的物理存活时间超过限值,将被销毁。
long phyConnectTimeMillis = currentTimeMillis - connection.connectTimeMillis;
if (phyConnectTimeMillis > phyTimeoutMillis) {
evictConnections[evictCount++] = connection;
continue;
}
}
long idleMillis = currentTimeMillis - connection.lastActiveTimeMillis; //空闲时间
//如果空闲时间过短,直接跳过
if (idleMillis < minEvictableIdleTimeMillis
&& idleMillis < keepAliveBetweenTimeMillis
) {
break;
}
if (idleMillis >= minEvictableIdleTimeMillis) { //如果连接空闲时间超过minEvictableIdleTimeMillis
if (checkTime && i < checkCount) { //留尾部的minIdle个连接先不销毁
evictConnections[evictCount++] = connection;
continue;
} else if (idleMillis > maxEvictableIdleTimeMillis) { //尾部的minIdle个连接如果连接空闲时间>maxEvictableIdleTimeMillis,也会被销毁
evictConnections[evictCount++] = connection;
continue;
}
}
if (keepAlive && idleMillis >= keepAliveBetweenTimeMillis) { //如果连接空闲时间未超过minEvictableIdleTimeMillis,但超过了keepAliveBetweenTimeMillis就要进行探活
keepAliveConnections[keepAliveCount++] = connection;
}
}
} finally {
lock.unlock();
}
if (evictCount > 0) { //如果有需要销毁的,则进行关闭连接操作。
for (int i = 0; i < evictCount; ++i) {
DruidConnectionHolder item = evictConnections[i];
Connection connection = item.getConnection();
JdbcUtils.close(connection);
destroyCountUpdater.incrementAndGet(this);
}
Arrays.fill(evictConnections, null);
}
if (keepAliveCount > 0) { //如果有需要探测的,则进行探测。探活的,则放回可用池;探不活的,直接关闭,并通知创建。
// keep order
for (int i = keepAliveCount - 1; i >= 0; --i) {
//此处省略一万行
}
}
if (needFill) {
lock.lock();
try {
int fillCount = minIdle - (activeCount + poolingCount + createTaskCount); //需要补充创建的连接个数
for (int i = 0; i < fillCount; ++i) {
emptySignal(); //给CreateConnectionThread发送empty条件信号来创建连接
}
} finally {
lock.unlock();
}
}
}
3.3.2 removeAbandoned()连接泄漏检测
连接泄露检查,打开removeAbandoned
功能,连接从连接池借出后,长时间不归还,将触发强制关闭其staement
并归还。回收周期随timeBetweenEvictionRunsMillis
进行,如果连接借出时间起超过removeAbandonedTimeout
,则强制关闭其staement
并归还。对性能会有一些影响,建议怀疑存在泄漏之后再打开,不建议在生产环境中使用。
java
//强制归还泄漏(借出后长时间未归还)的连接
public int removeAbandoned() {
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;
}
long timeMillis = (currrentNanos - pooledConnection.getConnectedTimeNano()) / (1000 * 1000); //连接借出时间
if (timeMillis >= removeAbandonedTimeoutMillis) {
iter.remove();
abandonedList.add(pooledConnection);
}
}
} finally {
activeConnectionLock.unlock();
}
if (abandonedList.size() > 0) {
for (DruidPooledConnection pooledConnection : abandonedList) {
JdbcUtils.close(pooledConnection); //强制归还
}
}
return removeCount;
}
4 获取连接流程getConnection()
连接池最核心的功能就是连接的获取与回收。我们直接看getConnectionDirect()
,它负责获取一个可用的连接。
java
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
for (;;) { //如果某次获取到的连接无效,一般会丢弃该连接并重新获取。
DruidPooledConnection poolableConnection;
try {
poolableConnection = getConnectionInternal(maxWaitMillis); //获取连接
} catch (GetConnectionTimeoutException ex) {
//......
}
if (testOnBorrow) { //如果testOnBorrow=true,则进行探测。
boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validate) {
discardConnection(poolableConnection.holder); //探测失败,则丢弃此连接并重新获取。
continue;
}
} else {
if (testWhileIdle) { //如果testWhileIdle=true且空闲时间>timeBetweenEvictionRunsMillis,则进行探测。
final DruidConnectionHolder holder = poolableConnection.holder;
long idleMillis = currentTimeMillis - lastActiveTimeMillis;
long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;
if (idleMillis >= timeBetweenEvictionRunsMillis
|| idleMillis < 0 // unexcepted branch
) {
boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validate) {
discardConnection(poolableConnection.holder); //如果探测失败,则丢弃连接并重新获取。
continue;
}
}
}
}
//是否打开连接泄露检查。DestroyConnectionThread如果检测到连接借出时间超过removeAbandonedTimeout,则强制归还连接到连接池中。
//对性能会有一些影响,建议怀疑存在泄漏之后再打开,不建议在生产环境中使用。
if (removeAbandoned) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); //保存发起方的线程栈
poolableConnection.connectStackTrace = stackTrace;
poolableConnection.setConnectedTimeNano(); //重置借出时间
activeConnectionLock.lock();
try {
activeConnections.put(poolableConnection, PRESENT); //放进activeConnections
} finally {
activeConnectionLock.unlock();
}
}
return poolableConnection;
}
}
4.1 getConnectionInternal()获取一个连接
getConnectionInternal()
负责从连接池获取一个连接(但该连接并不保证可用),并包装成DruidPooledConnection
。
java
private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
final long nanos = TimeUnit.MILLISECONDS.toNanos(maxWait);
final int maxWaitThreadCount = this.maxWaitThreadCount;
DruidConnectionHolder holder;
for (boolean createDirect = false;;) {
try {
//......
if (maxWaitThreadCount > 0
&& notEmptyWaitThreadCount >= maxWaitThreadCount) { //如果等待获取连接的线程数超过maxWaitThreadCount,则抛出异常
throw new SQLException("maxWaitThreadCount " + maxWaitThreadCount + ", current wait Thread count "
+ lock.getQueueLength());
}
//从可用连接池里获取连接,如果没有则阻塞等待。
if (maxWait > 0) {
holder = pollLast(nanos);
} else {
holder = takeLast();
}
//......
} //......
break;
}
//......
holder.incrementUseCount(); //连接的使用次数+1
DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder); ////包装成一个DruidPooledConnection对象
return poolalbeConnection;
}
4.2 takeLast()阻塞等待尾部连接
java
//阻塞等待尾部连接
DruidConnectionHolder takeLast() throws InterruptedException, SQLException {
try {
while (poolingCount == 0) { //如果没有可用连接,就发送个empty条件信号给CreateConnectionThread,并等待notEmpty条件信号
emptySignal(); // send signal to CreateThread create connection
notEmptyWaitThreadCount++;
try {
notEmpty.await(); // signal by recycle or creator
} finally {
notEmptyWaitThreadCount--;
}
//......
}
} catch (InterruptedException ie) {
//......
}
//移除尾部连接
decrementPoolingCount();
DruidConnectionHolder last = connections[poolingCount];
connections[poolingCount] = null;
return last;
}
至此,向请求线程返回一个可用的连接DruidPooledConnection。
5 执行&异常处理
如下为Mybatis执行SQL的核心函数(SqlSessionTemplate$SqlSessionInterceptor.invoke()
):
java
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//......
try {
Object result = method.invoke(sqlSession, args); //下调DruidPooledPreparedStatement.execute()执行SQL
unwrapped = result;
} catch (Throwable var11) {
unwrapped = ExceptionUtil.unwrapThrowable(var11);
//......
throw (Throwable)unwrapped; //如果发生异常,继续上抛
} finally {
if (sqlSession != null) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); //关闭连接,对应Druid是归还。
}
}
return unwrapped;
}
}
- method.invoke()会通过Druid获取连接,并调用
DruidPooledPreparedStatement.execute()
; - 执行结束后,close连接,此时会触发Druid的连接归还;
- 执行中如果发生异常,继续向上抛。
5.1 DruidPooledPreparedStatement.execute()
此时,进入Druid连接中statement的execute(),如果发生异常进入checkException()
。
java
public boolean execute() throws SQLException {
conn.beforeExecute();
try {
return stmt.execute(); //执行SQL
} catch (Throwable t) {
errorCheck(t);
throw checkException(t); //如果发生异常,调用DruidDataSource.handleConnectionException()对连接进行处理,并继续上抛
} finally {
conn.afterExecute();
}
}
5.1.1 DruidDataSource.handleConnectionException()
java
public void handleConnectionException(DruidPooledConnection pooledConnection, Throwable t, String sql) throws SQLException {
//......
if (t instanceof SQLException) {
SQLException sqlEx = (SQLException) t;
// exceptionSorter.isExceptionFatal
if (exceptionSorter != null && exceptionSorter.isExceptionFatal(sqlEx)) { //判断是否是致命异常
handleFatalError(pooledConnection, sqlEx, sql); //如果是致命异常,则销毁
}
throw sqlEx;
} else {
throw new SQLException("Error", t);
}
}
java
//对致命异常进行处理
protected final void handleFatalError(DruidPooledConnection conn, SQLException error, String sql ) throws SQLException {
final DruidConnectionHolder holder = conn.holder;
//......
if (requireDiscard) {
if (holder != null && holder.statementTrace != null) {
try {
for (Statement stmt : holder.statementTrace) { //关闭连接内的所有的statement
JdbcUtils.close(stmt);
}
} finally {
}
}
emptySignalCalled = this.discardConnection(holder); //销毁
}
LOG.error("{conn-" + (holder != null ? holder.getConnectionId() : "null") + "} discard", error);
//......
}
6 回收连接流程recycle()
使用DruidPooledConnection
连接进行SQL操作后,会调用DruidPooledConnection.recycle()
进行回收操作。
java
public void recycle() throws SQLException {
DruidConnectionHolder holder = this.holder;
DruidAbstractDataSource dataSource = holder.getDataSource();
dataSource.recycle(this);
//......
}
6.1 DruidDataSource.recycle()
java
/**
* 回收连接
* 1)重置连接;2)归还到连接池。
*/
protected void recycle(DruidPooledConnection pooledConnection) throws SQLException {
final DruidConnectionHolder holder = pooledConnection.holder;
final Connection physicalConnection = holder.conn;
try {
//归还前重置连接
holder.reset();
if (phyMaxUseCount > 0 && holder.useCount >= phyMaxUseCount) { //限制连接的最大使用次数。超过此值,会被直接关闭。
discardConnection(holder);
return;
}
if (testOnReturn) { //如果testOnReturn=true,归还前也检测下
//......
}
final long currentTimeMillis = System.currentTimeMillis();
if (phyTimeoutMillis > 0) { //检测物理存活时间
long phyConnectTimeMillis = currentTimeMillis - holder.connectTimeMillis;
if (phyConnectTimeMillis > phyTimeoutMillis) {
discardConnection(holder);
return;
}
}
lock.lock();
try {
result = putLast(holder, currentTimeMillis); //归还到连接池
recycleCount++;
} finally {
lock.unlock();
}
if (!result) {
JdbcUtils.close(holder.conn);
}
} catch (Throwable e) {
//......
}
}
6.2 将连接放入可用连接池尾部,并发送notEmpty条件信号
java
//将连接放入可用连接池尾部,并发送notEmpty条件信号
boolean putLast(DruidConnectionHolder e, long lastActiveTimeMillis) {
if (poolingCount >= maxActive || e.discard) {
return false;
}
e.lastActiveTimeMillis = lastActiveTimeMillis; //重置最近活跃时间
connections[poolingCount] = e; //归还到尾部
incrementPoolingCount();
notEmpty.signal();
notEmptySignalCount++;
return true;
}
至此,连接已成功回收。
7 总结
7.1 整个连接池的核心操作
- init()初始化 :1)创建
initialSize
个连接;2)启动LogStatsThread
、CreateConnectionThread
、DestroyConnectionThread
三个线程; - getConnection()获取连接 :获取后会从连接池移除,
Connection
只能归当前线程所用; - recycle()回收连接:放回连接池后,其他线程就可以再次获取该连接重复利用了。
7.2 条件信号协作
- 获取连接时 :如果连接池里没有连接,会发出
empty
条件信号,并等待notEmpty
条件信号。CreateConnectionThread
收到empty
信号后,如果满足条件则创建一个新连接,也会发出notEmpty
条件信号;如果不满足,则忽略此次empty
信号。 - 回收连接时 :连接放回连接池后,会发出
notEmpty
条件信号。如果有请求在阻塞等待获取连接,此时会被唤醒,从而获取连接。
7.3 几处检测和销毁逻辑
- 借出时:
- 如果
testOnBorrow
,则探测; - 如果(
testWhileIdl
e = true && 空闲时间 >timeBetweenEvictionRunsMillis
)则进行探测;
- 如果
- 执行时:
- 如果抛出异常且
exceptionSorter
判断是致命异常,就调用handleFatalError()
进行销毁;
- 如果抛出异常且
- 归还时:
- 如果连接使用次数超过
phyMaxUseCount
,则销毁; - 如果
testOnReturn
=true,则探测; - 如果连接建立时间走过
phyTimeoutMillis
,则销毁;
- 如果连接使用次数超过
- DestroyConnectionThread每隔timeBetweenEvictionRunsMillis扫描一次连接池中的空闲连接 :
- 如果物理存活时间超过
phyTimeoutMillis
,则销毁; - 如果(
keepAlive
&& 空闲时间 >=keepAliveBetweenTimeMillis
),则进行探测; - 如果空闲时间 >=
minEvictableIdleTimeMillis
,则销毁(但要保证留下minIdle
个);而如果空闲时间超过maxEvictableIdleTimeMillis
则必须销毁; - 如果(
removeAbandoned
== true && 连接借出时间 >removeAbandonedTimeoutMillis
),则被强制关闭其statement
并归还。
- 如果物理存活时间超过
8 常用&推荐配置
8.1 常用配置
官方完整介绍见:github.com/alibaba/dru...
8.2 推荐配置
xml
<bean id="userdataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本属性 -->
<property name="name" value="userdataSource" />
<property name="url" value="${userdataSource_url}" />
<property name="driverClassName" value="com.zhuanzhuan.arch.kms.jdbc.mysql.Driver" />
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1" />
<property name="minIdle" value="3" />
<property name="maxActive" value="20" />
<!-- 获取连接的等待超时时间,单位是毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 -->
<property name="maxWait" value="60000" />
<!-- 探测的SQL -->
<property name="validationQuery" value="SELECT 1" />
<!-- 获取连接时,执行validationQuery检测连接是否有效 -->
<property name="testOnBorrow" value="false" />
<!-- 获取连接时,如果空闲时间超过timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效 -->
<property name="testWhileIdle" value="true" />
<!-- 归还连接时,执行validationQuery检测连接是否有效 -->
<property name="testOnReturn" value="false" />
<!-- 定期检测的时间间隔,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 定期检测时,如果空闲时间超过此值则进行销毁(但要保证留下minIdle个连接),单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
</bean>
此配置说明:
- 线程池刚启动时会创建1个(
initialSize
)连接,随着程序的运行,池不忙时也会保持最少3个(minIdle
)空闲连接,但总连接数(包括空闲和在用)不超过20个(maxActive
); - 获取连接时 :
- 如果连接池没有空闲连接,最长等待60秒(
maxWait
); - 【主动】如果获取到的连接空闲时间大于60秒(
timeBetweenEvictionRunsMillis
),则执行validationQuery
检测连接是否还有效(有效则使用,无效则销毁);
- 如果连接池没有空闲连接,最长等待60秒(
- 执行SQL时 :
- 【被动】如果发生致命异常(默认
exceptionSorter
=MySqlExceptionSorter
,如CommunicationsException
),则销毁该连接;
- 【被动】如果发生致命异常(默认
DestroyConnectionThread
每隔60秒(timeBetweenEvictionRunsMillis
)扫描一次连接池中的空闲连接:- 【主动】如果空闲时间超过300秒(
minEvictableIdleTimeMillis
),则销毁(但要保证留下minIdle
=3个);而如果空闲时间超过7小时(maxEvictableIdleTimeMillis
默认为7小时)则必须销毁。
- 【主动】如果空闲时间超过300秒(
9 监控
Druid通过SPI开放了很多扩展点,我们架构部基于此封装了监控组件,直接上报到Prometheus。效果如下:
关于作者
杜云杰,高级架构师,转转架构部负责人,转转技术委员会执行主席,腾讯云MVP。负责服务治理、MQ、云平台、APM、IM、分布式调用链路追踪、监控系统、配置中心、分布式任务调度平台、分布式ID生成器、分布式锁等基础组件。微信号:waterystone
,欢迎建设性交流。
道阻且长,拥抱变化;而困而知,且勉且行。