3、Druid原理分析
- Druid是JDBC组件,包括三个部分:
-
- DruidDriver: 代理Driver,能够提供基于Filter-Chain模式的插件体系
- DruidDataSource: 高效可管理的数据库连接池
- SQL Parser: Druid内置使用SQL Parser来实现防御SQL注入(WallFilter),合并统计没有参数化的SQL(StatFilter的mergeSql),SQL格式化,分库分表
3.1 durid优势介绍
1.Druid连接池是阿里巴巴开源的数据库连接池项目。Druid连接池为监控而生,内置强大的监控功能,监控特性不影响性能。功能强大,能防SQL注入,内置Loging能诊断Hack应用行为。 2.Druid连接池是[阿里巴巴内部唯一使用的连接池],在内部数据库相关中间件TDDL/DRDS 都内置使用强依赖了Druid连接池,经过阿里内部数千上万的系统大规模验证,[经过历年双十一超大规模并发验证]。 3.它有贴心的错误提示——当连接不够用时,申请链接超时报错,Druid连接池能够报错会告诉你当前RunningSQL有哪些,当前连接池的水平信息。通过错误日志很方便知道系统瓶颈在哪里。 4.Druid连接池最初就是为监控系统采集jdbc运行信息而生的,它内置了StatFilter 功能,能采集非常完备的连接池执行信息Druid连接池内置了能和Spring/Servlet关联监控的实现,使得监控Web应用特别方便Druid连接池内置了一个监控页面,提供了非常完备的监控信息,可以快速诊断系统的瓶颈。 5. 监控不影响性能——Druid增加StatFilter之后,能采集大量统计信息,同时对性能基本没有影响。StatFilter对CPU和内存的消耗都极小,对系统的影响可以忽略不计。监控不影响性能是Druid连接池的重要特性。 6. SQL参数化合并监控——实际业务中,如果SQL不是走PreparedStatement,SQL没有参数化,这时SQL需要参数化 合并监控才能真实反映业务情况。如下SQL: select * from t where id = 1 select * from t where id = 2 select * from t where id = 3 参数化后: select * from t where id = ? 参数化合并监控是基于SQL Parser语法解析实现的,是Druid连接池独一无二的功能。 7.执行次数、返回行数、更新行数和并发监控——StatFilter能采集到每个SQL的执行次数、返回行数总和、更新行数总和、执行中次数和和最大并发。并发监控的统计是在SQL执行开始对计数器加一,结束后对计数器减一实现的。可以采集到每个SQL的当前并发和采集期间的最大并发。 8.防SQL注入 SQL注入攻击是黑客对数据库进行攻击的常用手段,Druid连接池内置了WallFilter 提供防SQL注入功能,在不影响性能的同时防御SQL注入攻击。 8.1 基于语意的防SQL注入 Druid连接池内置了一个功能完备的SQL Parser,能够完整解析mysql、sql server、oracle、postgresql的语法,通过语意分析能够精确识别SQL注入攻击。 8.2 极低的漏报率和误报率 基于SQL语意分析,大量应用和反馈,使得Druid的防SQL注入拥有极低的漏报率和误报率。 8.3 防注入对性能影响极小 内置参数化后的Cache、高性能手写的Parser,使得打开防SQL注入对应用的性能基本不受影响。
- 配置参数: Druid的DataSource:com.alibaba.druid.pool.DruidDataSource
配置参数 | 缺省值 | 说明 |
name | 如果存在多个数据源,监控时可以通过name属性进行区分,如果没有配置,将会生成一个名字:"DataSource-"+System.identityHashCode(this) | |
jdbcUrl | 连接数据库的url,不同的数据库url表示方式不同: mysql:jdbc:mysql://192.16.32.128:3306/druid2 oracle : jdbc:oracle:thin:@192.16.32.128:1521:druid2 | |
username | 连接数据库的用户名 | |
password | 连接数据库的密码,密码不出现在配置文件中可以使用ConfigFilter 直接把数据库密码写在配置文件容易导致安全问题,DruidDruiver和DruidDataSource都支持PasswordCallback | |
driverClassName | 根据jdbcUrl自动识别 | 可以不配置,Druid会根据jdbcUrl自动识别dbType,选择相应的driverClassName |
initialSize | 0 | 初始化时建立物理连接的个数. 初始化过程发生在:显示调用init方法;第一次getConnection |
maxActive | 8 | 最大连接池数量 |
minIdle | 最小连接池数量 | |
maxWait | 获取连接时最大等待时间,单位毫秒. 配置maxWait默认使用公平锁等待机制,并发效率会下降.可以配置useUnfairLock为true使用非公平锁 | |
poolPreparedStatements | FALSE | 是否缓存preparedStatement,即PSCache. PSCache能够提升对支持游标的数据库性能. 在Oracle中使用,在MySQL中关闭 |
maxOpenPreparedStatements | -1 | 要启用PSCache,必须配置参数值>0,poolPreparedStatements自动触发修改为true. Oracle中可以配置数值为100,Oracle中不会存在PSCache过多的问题 |
validationQuery | 用来检测连接的是否为有效SQL,要求是一个查询语句 如果validationQuery=null,那么testOnBorrow,testOnReturn,testWhileIdle都不会起作用 | |
testOnBorrow | TRUE | 申请连接时执行validationQuery检测连接是否有效,会降低性能 |
testOnReturn | FALSE | 归还连接时执行validationQuery检测连接是否有效,会降低性能 |
testWhileIdle | FALSE | 申请连接时,空闲时间大于timeBetweenEvictionRunsMillis时,执行validationQuery检测连接是否有效 不影响性能,保证安全性,建议配置为true |
timeBetweenEvictionRunsMillis | Destroy线程会检测连接的间隔时间 testWhileIdle的判断依据 | |
connectionInitSqls | 物理连接初始化时执行SQL | |
exceptionSorter | 根据dbType自动识别 | 当数据库跑出不可恢复的异常时,抛弃连接 |
filters | 通过别名的方式配置扩展插件,属性类型是字符串: 常用的插件: 监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall | |
proxyFilters | 类型是List<com.alibaba.druid.filter.Filter>,如果同时配置了filters和proxyFilters是组合关系,不是替换关系 |
3.2 Druid整体核心类图结构
- 过滤器的功能是在执行任何连接或者语句等相关方法之前都需要先执行对应的过滤器
- 原始数据库连接,语句等在Druid中都进行了包装和代理
- StatFilter类对执行的连接或者语句等进行信息统计,并将统计的结果放入JdbcDataSourceStat 和JdbcSqlStat中,供后续监控显示使用
- Druid使用了MBean方式(管理bean的功能)来实现监控和管理对应的信息,MBean介绍参考https://www.jianshu.com/p/6aff920def9b
3.3 Druid如何管理数据库连接
3.3.1 核心类说明
3.3.1.1 DruidDataSource类
这个类也是druid连接池基础类,扩展了一些其父类的功能,几乎所有的有关连接池管理的操作都在此类完成。
属性表:
connectCount | 一共成功从该池获取了多少次连接(只要获取成功一次,就累加一次) |
recycleCount | 一共成功归还了多少次连接到该池(成功归还一次,累加一次) |
removeAbandonedCount | 在removeAbandoned开启的情况下,被检查后强制归还的连接数 |
connections(数组) | 最终池子里没有被使用的闲置连接存放的地方,类型是DruidConnectionHolder |
poolingCount | pollingCount就是指上面connections的真实数量 |
activeCount | 当前处于借出状态的连接数(也即是被拿出去使用的连接数),poolingCount+activeCount就是当前该池子里一共有多少个连接,不能超过maxActive,参考主流程3 |
discardCount | 被丢弃的连接数,触发丢弃的地方有很多,比如流程1.4、流程4.1 |
evictConnections(数组) | 参考流程4.1 |
keepAliveConnections(数组) | 参考流程4.1 |
createConnectionThread(线程) | 生产连接的守护线程,参考主流程3 |
destroyConnectionThread(线程) | 抛弃连接的守护线程,参考主流程4 |
logStatsThread(线程) | 打印连接池各项监控指标日志的守护线程,参考主流程2,默认关闭 |
initedLatch(CountDownLatch) | 倒计数器,用来保证createConnectionThread和destroyConnectionThread两个守护线程全部开启成功。 |
enable | 连接池对象是否可用,在连接池整体close后,该值为false,表示已关闭的连接池不可用。 |
keepAlive | 参考流程4.1 |
loadSpifilterSkip | 是否启用通过SPI机制加载责任链上的filter,默认开启 |
表2-1
方法表:
init | 初始化整个连接池,参考主流程2 |
initFromSPIServiceLoader | 通过SPI机制加载责任链中的filters |
initValidConnectionChecker | 初始化(适配)检测器,参考流程1.3中的init-checker |
createAndLogThread | 启动上面表2-1里的logStatsThread线程 |
createAndStartCreatorThread | 启动上面表2-1里的createConnectionThread线程 |
createAndStartDestroyThread | 启动上面表2-1里的destroyConnectionThread线程 |
getConnection | 获取连接方法,参考主流程1 |
getConnectionDirect | 获取连接方法(通过getConnection触发),参考主流程1 |
pollLast | 真正从池子里获取连接对象的方法(通过getConnectionDirect触发),参考流程1.2 |
putLast | 真正归还连接进池子的方法(通过recycle触发),参考主流程5 |
put | 新增连接对象放进池子里的方法,通过主流程3触发 |
close | 连接池关闭,不再提供服务,迅速干掉所有连接进入贤者模式。 |
recycle | 连接回收方法,通过下面DruidPooledConnection类的close方法触发,参考主流程5 |
shrink | 连接池瘦身,参考主流程4、流程4.1 |
removeAbandoned | 回收长期未回收的连接,默认关闭不检查,通过表1-1里的removeAbandoned属性控制 |
emptySignal | 触发表1-1里的empty执行signal,用于唤起主流程3新增连接 |
表2-2
3.3.1.2 DruidConnectionHolder类
最终存放进池子里的基本类型,该类持有驱动产生的真实Connection对象,同时提供一些连接池需要的标记性的属性。
属性表:
dataSource(DruidDataSource) | 本身包含一个持有自己实例的连接池对象 |
conn(Connection) | 真实的驱动连接对象 |
lastActiveTimeMillis | 上次活动时间,该值在归还连接时会被刷新一次,参考主流程5,除此之外在一句sql执行结束后,这个值也会被刷新 |
defaultReadOnly | 是否默认为只读模式,默认不是 |
defaultAutoCommit | 是否默认开启AutoCommit,默认开启 |
discard | 当前连接是否已被抛弃 |
表3-1
方法表:
reset | 在连接对象被归还时,由于使用时可能被业务代码人为的改动一些属性(比如autoCommit等)需要把一些属性重新置为默认值,就需要该方法 |
表3-2
3.3.1.4 DruidPooledConnection类
对外暴露给业务方的连接对象包装类,实际上其内部是包了一层上面的holder对象。
属性表:
conn(Connection) | 实际的驱动连接对象,通过下面持有的holder对象获得并赋值 |
holder(DruidConnectionHolder) | 持有的holder对象 |
disable | 标记是否可用 |
ownerThread | 标记最初获取到自己的那个线程(用于决定在close时走下方的close还是syncClose,参考主流程5) |
closed | 标记是否已被关闭 |
running | 标记是否正在运行中,running被置为true的地方,就是执行excute方法时。 |
abandoned | 标记是否已被检查并丢弃,参考流程4.2 |
表4-1
方法表:
close | 关闭该连接,将实际连接归还至连接池,参考主流程5 |
syncClose | 如果是别的线程执行close方法,就得启用该方法去做,该方法与上面close的区别就是加了锁控制,参考流程5 |
recycle | 实际触发datasource.recycle方法的方法,参考流程5 |
常规操作 | 有createStatement、commit、rollback等等常规操作,本篇文章不涉及到,仅做连接池的说明,暂时忽略。 |
3.3.2 数据库连接管理流程
3.3.2.1 主流程1:获取连接流程
1、调用init进行连接池的初始化,然后运行责任链上的每一个filter,最终执行getConnectionDirect获取真正的连接对象,
在如果开启了testOnBorrow,则每次都会去测试连接是否可用(开启影响性能,一般mysql服务端长连保活时间是8h),
如果testOnBorrow没有被置为true,则会进行testWhileIdle的检查(这一项官方建议设置为true,缺省值也是true),检查时会判断当前连接对象距离上次被使用的时间是否超过规定检查的时间,若超过,则进行检查一次,这个检查时间通过timeBetweenEvictionRunsMillis来控制,默认60s,
为了防止不必要的扩容,在mysql服务端长连接够用的情况下,建议把池子的最小闲置连接数minIdle和最大连接数maxActive设置成一样的
3.3.2.2 流程1.1:责任链
每个DruidAbstractDataSource里都包含filters属性,比如例子中dataSource的getConnection方法在触发的时候就会利用FilterChain把每个filter里的dataSource_getConnection给执行一遍dataSource.getConnectionDirect(查看源码)
3.3.2.3 流程1.2:从池中获取连接的流程
流程1.2(对应源代码:代码段1-3)
通过getConnectionInternal方法从池子里获取真正的连接对象,
druid支持两种方式新增连接,一种是通过开启不同的守护线程通过await、signal通信实现,另一种是直接通过线程池异步新增,这个方式通过在初始化druid时传入asyncInit=true,再把一个线程池对象赋值给createScheduler,就成功启用了这种模式
druid如何防止在获取不到连接时阻塞过多的业务线程?
通过上面的流程图和流程描述,如果非常极端的情况,池子里的连接完全不够用时,会阻塞过多的业务线程,甚至会阻塞超过maxWait这么久,有没有一种措施是可以在连接不够用的时候控制阻塞线程的个数,超过这个限制后直接报错,而不是陷入等待呢?
druid其实支持这种策略的,在maxWaitThreadCount属性为默认值(-1)的情况下不启用,如果maxWaitThreadCount配置大于0,表示启用,这是druid做的一种丢弃措施,丢弃连接完全不够用导阻塞的业务线程过多
每次在pollLast方法里陷入等待前会把属性notEmptyWaitThreadCount进行累加,阻塞结束后会递减,由此可见notEmptyWaitThreadCount就是表示当前等待可用连接时阻塞的业务线程的总个数,而getConnectionInternal在每次调用pollLast前都会判断这样一段代码:
if (maxWaitThreadCount > 0 && notEmptyWaitThreadCount >= maxWaitThreadCount) {
connectErrorCountUpdater.incrementAndGet(this);
throw new SQLException("maxWaitThreadCount " + maxWaitThreadCount + ", current wait Thread count "
+ lock.getQueueLength()); //直接抛异常,而不是陷入等待状态阻塞业务线程
}
3.3.2.4 流程1.3:连接可用性测试
init-checker
testConnectionInternal
然后回到本节探讨的方法:流程1.3对应的testConnectionInternal
3.3.2.5流程1.4:抛弃连接
经过流程1.3返回的测试结果,如果发现连接不可用,则直接触发抛弃连接逻辑,
3.3.2.5 主流程2 初始化连接池
实例化的时候会初始化全局的重入锁lock,利用该锁保证线程安全,初始化连接池的时候首先会进行双重检查是否已经初始化过,若没有,则进行连接池的初始化,
然后初始化了三个数组,容积都为maxActive,
首先connections就是用来存放池子里连接对象的,
evictConnections用来存放每次检查需要抛弃的连接(结合流程4.1理解),
keepAliveConnections用于存放需要连接检查的存活连接(同样结合流程4.1理解),
然后生成初始化数(initialSize)个连接,放进connections,
3.3.2.6主流程3:添加连接的守护线程
在主流程2(init初始化阶段)时就开启了该流程,该流程独立运行,大部分时间处于等待状态,不会抢占cpu,但是当连接不够用时,就会被唤起追加连接,成功创建连接后将会唤醒其他正在等待获取可用连接的线程,比如:
结合流程1.2来看,当连接不够用时,会通过empty.signal唤醒该线程进行补充连接,然后通过notEmpty阻塞自己,当该线程补充连接成功后,又会对阻塞在notEmpty上的线程进行唤醒,让其进入锁竞争状态,简单理解就是一个生产-消费模型。
3.3.2.7主流程4:抛弃连接的守护线程
主流程4(对应源代码:代码段4-1)
3.3.2.8流程4.1:连接池瘦身,检查连接是否可用以及丢弃多余连接
整个过程如下:
3.3.2.9流程4.2:主动回收连接,防止内存泄漏
过程如下:
流程4.2(对应源代码:代码段4-3)
这个流程在removeAbandoned设置为true的情况下才会触发,用于回收那些拿出去的使用长期未归还(归还:调用close方法触发主流程5)的连接。
3.3.2.10主流程5:回收连接
这个流程通常是靠连接包装类DruidPooledConnection的close方法触发的,目标方法为recycle,流程图如下:
3.4 Druid监控
- druid提供了丰富的监控功能,这篇文章主要分析下监控功能的实现原理。
- 通过代理模式控制statement对象的访问。druid里的Statement、PreparedStatement、Connection等对象都通过代理模式访问。对应的druid的代理是StatementProxyImpl、PreparedStatementProxyImpl、ConnectionProxyImpl。
- 通过filter过滤器链增加监控和日志功能。
3.4.1 Connection
- Druid的连接对象PhysicalConnectionInfo的创建过程经过FilterChainImpl进行封装,核心是经过相关的Filter进行包装。
public PhysicalConnectionInfo createPhysicalConnection() throws SQLException {
String url = this.getUrl();
Properties connectProperties = getConnectProperties();
// 省略相关的代码
try {
// 创建物理链接
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);
} catch (SQLException ex) {
setCreateError(ex);
JdbcUtils.close(conn);
throw ex;
} catch (RuntimeException ex) {
setCreateError(ex);
JdbcUtils.close(conn);
throw ex;
} catch (Error ex) {
createErrorCountUpdater.incrementAndGet(this);
setCreateError(ex);
JdbcUtils.close(conn);
throw ex;
} finally {
long nano = System.nanoTime() - connectStartNanos;
createTimespan += nano;
creatingCountUpdater.decrementAndGet(this);
}
// 创建PhysicalConnectionInfo
return new PhysicalConnectionInfo(conn, connectStartNanos, connectedNanos, initedNanos, validatedNanos, variables, globalVariables);
}
// 通过FilterChainImpl进行装饰
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);
}
createCountUpdater.incrementAndGet(this);
return conn;
}
FilterChainImpl返回Connection对象,通过PhysicalConnectionInfo封装。
public class FilterChainImpl implements FilterChain {
public ConnectionProxy connection_connect(Properties info) throws SQLException {
if (this.pos < filterSize) {
return nextFilter()
.connection_connect(this, info);
}
Driver driver = dataSource.getRawDriver();
String url = dataSource.getRawJdbcUrl();
Connection nativeConnection = driver.connect(url, info);
if (nativeConnection == null) {
return null;
}
return new ConnectionProxyImpl(dataSource, nativeConnection, info, dataSource.createConnectionId());
}
FilterChainImpl内部经过Filter进行封装,核心是通过Filter进行封装。
在经过Filter封装前创建的时ConnectionProxyImpl对象,外层进行Filter的包装。
Connection的封装对象是ConnectionProxyImpl对象。
FilterChainImpl内部经过Filter进行封装,核心是通过Filter进行封装。
在经过Filter封装前创建的时PreparedStatementProxyImpl对象,外层进行Filter的包装。
PreparedStatement的代理对象是PreparedStatementProxyImpl。
3.4.2 以一次查询为例分析
总结
初始化数据源会创建DruidDataSource对象。获取连接的时候会调用DruidDataSource.getConnection()方法,该方法会返回DruidPooledConnection连接对象。
接着会调用该对象的prepareStatement(String sql)方法。prepareStatement方法会判断是否使用池中缓存预编译对象构建DruidPooledPreparedStatement对象并返回。
接着会调用DruidPooledPreparedStatement的execute()方法执行本次sql。execute()方法会调用
FilterEventAdapter的preparedStatement_execute方法。该方法主要调用对应的Filter实现执行前、执行后操作数据记录等。
监控的过滤器实现为StatFilter。
执行前对应internalBeforeStatementExecute方法。
执行后对呀internalAfterStatementExecute方法。
会记录sql的执行情况并保存这些结果到JdbcDataSourceStat中。
后续查看sql的执行结果可以通过获取该对象来获得。
3.4.2.1 源码查看
当我们请求接口查询用户信息时,会执行DruidDataSource 的getConnection()方法。该方法会返回一个DruidPooledConnection对象。拿到连接对象后会执行该对象的prepareStatement(String sql)方法。
DruidPooledConnection.prepareStatement
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
//检查状态
checkState();
//生成一个预编译对象
PreparedStatementHolder stmtHolder = null;
PreparedStatementKey key = new PreparedStatementKey(sql, getCatalog(), MethodType.M1);
boolean poolPreparedStatements = holder.isPoolPreparedStatements();
//是否使用池中缓存预编译对象
if (poolPreparedStatements) {
stmtHolder = holder.getStatementPool().get(key);
}
//当预编译对象为空时。conn.prepareStatement(sql)会创建jdbcSqlStat。保存sql执行基本信息
if (stmtHolder == null) {
try {
stmtHolder = new PreparedStatementHolder(key, conn.prepareStatement(sql));
holder.getDataSource().incrementPreparedStatementCount();
} catch (SQLException ex) {
handleException(ex, sql);
}
}
//初始化Statement。会设置查询时间等。
initStatement(stmtHolder);
//创建一个DruidPooledPreparedStatement预编译对象返回给调用方。执行后续流程。
DruidPooledPreparedStatement rtnVal = new DruidPooledPreparedStatement(this, stmtHolder);
holder.addTrace(rtnVal);
return rtnVal;
}
DruidPooledConnection的prepareStatement方法返回的DruidPooledPreparedStatement预编译对象后。会执行DruidPooledPreparedStatement对象的execute()方法
@Override
public boolean execute() throws SQLException {
//检查连接是否开启
checkOpen();
//增加执行次数
incrementExecuteCount();
transactionRecord(sql);
conn.beforeExecute();
try {
//该方法会调用Filter责任链执行preparedStatement_execute方法
return stmt.execute();
} catch (Throwable t) {
errorCheck(t);
throw checkException(t);
} finally {
conn.afterExecute();
}
}
@Override
public boolean preparedStatement_execute(FilterChain chain, PreparedStatementProxy statement) throws SQLException {
//这里会执行对应Filter的statementExecuteBefore方法。监控的Filter为StatFilter。该方法内部实现为StatFilter.internalBeforeStatementExecute(StatementProxy statement, String sql);
statementExecuteBefore(statement, statement.getSql());
//获取执行结果
boolean firstResult = chain.preparedStatement_execute(statement);
//同理执行对应Filter对应方法。此方法对应StatFilter的internalAfterStatementExecute方法
this.statementExecuteAfter(statement, statement.getSql(), firstResult);
//返回执行结果
return firstResult;
}
StatFilter的internalBeforeStatementExecute方法跟internalAfterStatementExecute方法会记录sql的执行情况并保存这些结果到JdbcDataSourceStat中。后续查看sql的执行结果可以通过获取该对象来获得。
//执行前
private final void internalBeforeStatementExecute(StatementProxy statement, String sql) {
JdbcDataSourceStat dataSourceStat = statement.getConnectionProxy().getDirectDataSource().getDataSourceStat();
dataSourceStat.getStatementStat().beforeExecute();
final ConnectionProxy connection = statement.getConnectionProxy();
final JdbcConnectionStat.Entry connectionCounter = getConnectionInfo(connection);
statement.setLastExecuteStartNano();
connectionCounter.setLastSql(sql);
JdbcStatContext statContext = JdbcStatManager.getInstance().getStatContext();
boolean inTransaction = false;
try {
inTransaction = !statement.getConnectionProxy().getAutoCommit();
} catch (SQLException e) {
LOG.error("getAutoCommit error", e);
}
if (sqlStat != null) {
sqlStat.setExecuteLastStartTime(System.currentTimeMillis());
sqlStat.incrementRunningCount();
if (inTransaction) {
sqlStat.incrementInTransactionCount();
}
}
StatFilterContext.getInstance().executeBefore(sql, inTransaction);
}
//执行后
private final void internalAfterStatementExecute(StatementProxy statement, boolean firstResult,
int... updateCountArray) {
final long nowNano = System.nanoTime();
final long nanos = nowNano - statement.getLastExecuteStartNano();
JdbcDataSourceStat dataSourceStat = statement.getConnectionProxy().getDirectDataSource().getDataSourceStat();
dataSourceStat.getStatementStat().afterExecute(nanos);
final JdbcSqlStat sqlStat = statement.getSqlStat();
if (sqlStat != null) {
sqlStat.incrementExecuteSuccessCount();
sqlStat.decrementRunningCount();
sqlStat.addExecuteTime(statement.getLastExecuteType(), firstResult, nanos);
statement.setLastExecuteTimeNano(nanos);
if ((!firstResult) && statement.getLastExecuteType() == StatementExecuteType.Execute) {
try {
int updateCount = statement.getUpdateCount();
sqlStat.addUpdateCount(updateCount);
} catch (SQLException e) {
LOG.error("getUpdateCount error", e);
}
} else {
for (int updateCount : updateCountArray) {
sqlStat.addUpdateCount(updateCount);
sqlStat.addFetchRowCount(0);
StatFilterContext.getInstance().addUpdateCount(updateCount);
}
}
long millis = nanos / (1000 * 1000);
if (millis >= slowSqlMillis) {
String slowParameters = buildSlowParameters(statement);
sqlStat.setLastSlowParameters(slowParameters);
String lastExecSql = statement.getLastExecuteSql();
if (logSlowSql) {
LOG.error("slow sql " + millis + " millis. " + lastExecSql + "" + slowParameters);
}
handleSlowSql(statement);
}
}
String sql = statement.getLastExecuteSql();
StatFilterContext.getInstance().executeAfter(sql, nanos, null);
Profiler.release(nanos);
}
3.4.3监控属性
3.5 其他
1、怎样使用Druid的内置监控页面
内置监控页面是一个Servlet,具体配置看这里:https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatViewServlet%E9%85%8D%E7%BD%AE
2、内置监控中的Web和Spring关联监控怎么配置?
Web关联监控配置
https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_%E9%85%8D%E7%BD%AEWebStatFilter
Spring关联监控配置
https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_Druid%E5%92%8CSpring%E5%85%B3%E8%81%94%E7%9B%91%E6%8E%A7%E9%85%8D%E7%BD%AE
3、怎么配置防御SQL注入攻击
Druid提供了WallFilter,它是基于SQL语义分析来实现防御SQL注入攻击的。具体配置看这里:https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE-wallfilter
4、Druid有没有参考配置
不同的业务场景需求不同,你可以使用我们的参考配置,但建议你仔细阅读相关文档,了解清楚之后做定制配置。https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_DruidDataSource%E5%8F%82%E8%80%83%E9%85%8D%E7%BD%AE
5、我想日志记录JDBC执行的SQL,如何配置
Druid提供了Log4jFilter、CommonsLogFilter和Slf4jFilter,具体配置看这里https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_LogFilter
6、我的程序可能产生连接泄漏了,有什么办法?
Druid提供了多种监测连接泄漏的手段,具体看这里:https://github.com/alibaba/druid/wiki/%E8%BF%9E%E6%8E%A5%E6%B3%84%E6%BC%8F%E7%9B%91%E6%B5%8B
ea
参考文档
git源码:
alibaba/druid pool analysis · Issue #232 · brettwooldridge/HikariCP · GitHub
https://github.com/alibaba/druid
技术博客