本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
druid --为监控而生,具体监控如何做
结合昨天FilterChainImpl执行的FilterEventAdapter的拦截器处理逻辑,今天针对源码中的StatFilter源码进行分析,关注拦截sql执行中的监控参数处理。
connection_connect
连接时的拦截方法
public ConnectionProxy connection_connect(FilterChain chain, Properties info) throws SQLException {
//连接代理类
ConnectionProxy connection = null;
long startNano = System.nanoTime();
long startTime = System.currentTimeMillis();
long nanoSpan;
long nowTime = System.currentTimeMillis();
//数据源统计
JdbcDataSourceStat dataSourceStat = chain.getDataSource().getDataSourceStat();
//连接之前 connectingCount 数量++ 连接的最大数量 连接的最近时间
dataSourceStat.getConnectionStat().beforeConnect();
try {
//执行连接 使用ConnectionProxyImpl连接代理类获取连接
connection = chain.connection_connect(info);
nanoSpan = System.nanoTime() - startNano;
} catch (SQLException ex) {
//连接异常记录
dataSourceStat.getConnectionStat().connectError(ex);
throw ex;
}
//创建连接之后 激活的count++
dataSourceStat.getConnectionStat().afterConnected(nanoSpan);
if (connection != null) {
//获取连接的详情
JdbcConnectionStat.Entry statEntry = getConnectionInfo(connection);
//数据源连接监控增加该连接
dataSourceStat.getConnections().put(connection.getId(), statEntry);
statEntry.setConnectTime(new Date(startTime));
statEntry.setConnectTimespanNano(nanoSpan);
statEntry.setEstablishNano(System.nanoTime());
statEntry.setEstablishTime(nowTime);
statEntry.setConnectStackTrace(new Exception());
//设置激活数量
dataSourceStat.getConnectionStat().setActiveCount(dataSourceStat.getConnections().size());
}
return connection;
}
connection_close
连接关闭的拦截方法
@Override
public void connection_close(FilterChain chain, ConnectionProxy connection) throws SQLException {
if (connection.getCloseCount() == 0) {
long nowNano = System.nanoTime();
//数据源统计
JdbcDataSourceStat dataSourceStat = chain.getDataSource().getDataSourceStat();
//closeCount数量修改
dataSourceStat.getConnectionStat().incrementConnectionCloseCount();
//获取连接的详情
JdbcConnectionStat.Entry connectionInfo = getConnectionInfo(connection);
long aliveNanoSpan = nowNano - connectionInfo.getEstablishNano();
//监控池移除改连接
JdbcConnectionStat.Entry existsConnection = dataSourceStat.getConnections().remove(connection.getId());
if (existsConnection != null) {
//激活的数量 激活的最小时间 属性修改
dataSourceStat.getConnectionStat().afterClose(aliveNanoSpan);
}
}
// 关闭连接
chain.connection_close(connection);
}
connection_commit
@Override
public void connection_commit(FilterChain chain, ConnectionProxy connection) throws SQLException {
chain.connection_commit(connection);
JdbcDataSourceStat dataSourceStat = chain.getDataSource().getDataSourceStat();
//commitCount数量++
dataSourceStat.getConnectionStat().incrementConnectionCommitCount();
}
@Override
public void connection_rollback(FilterChain chain, ConnectionProxy connection) throws SQLException {
chain.connection_rollback(connection);
JdbcDataSourceStat dataSourceStat = chain.getDataSource().getDataSourceStat();
//rollbackCount数量++
dataSourceStat.getConnectionStat().incrementConnectionRollbackCount();
}
internalBeforeStatementExecute
private final void internalBeforeStatementExecute(StatementProxy statement, String sql) {
JdbcDataSourceStat dataSourceStat = statement.getConnectionProxy().getDirectDataSource().getDataSourceStat();
//执行之前runningCount、concurrentMax属性设置
dataSourceStat.getStatementStat().beforeExecute();
//获取连接代理类
final ConnectionProxy connection = statement.getConnectionProxy();
//获取连接
final JdbcConnectionStat.Entry connectionCounter = getConnectionInfo(connection);
//设置最近执行时间
statement.setLastExecuteStartNano();
//设置最近执行sql
connectionCounter.setLastSql(sql);
if (connectionStackTraceEnable) {
//是否是可追踪的连接
connectionCounter.setLastStatementStatckTrace(new Exception());
}
// //////////SQL
//获取sql
JdbcSqlStat sqlStat = statement.getSqlStat();
if (sqlStat == null || sqlStat.isRemoved()) {
//如果没有sql统计 则创建 其中这块源码中有个有趣的点就是获取sqlstat的时候有个mergeSql的方法,如果有同样的sql的话会合并成一个SQLStatement吗?? TODO 后面研究一下
sqlStat = createSqlStat(statement, sql);
statement.setSqlStat(sqlStat);
}
//获取stat的上下文
JdbcStatContext statContext = JdbcStatManager.getInstance().getStatContext();
if (statContext != null) {
sqlStat.setName(statContext.getName());
sqlStat.setFile(statContext.getFile());
}
boolean inTransaction = false;
try {
//是否在事务中
inTransaction = !statement.getConnectionProxy().getAutoCommit();
} catch (SQLException e) {
LOG.error("getAutoCommit error", e);
}
if (sqlStat != null) {
//更新执行的最近时间
sqlStat.setExecuteLastStartTime(System.currentTimeMillis());
//runningCountUpdater、concurrentMaxUpdater 属性修改
sqlStat.incrementRunningCount();
if (inTransaction) {
//事务中的inTransactionCountUpdater属性++
sqlStat.incrementInTransactionCount();
}
}
// jdbc执行的count++
StatFilterContext.getInstance().executeBefore(sql, inTransaction);
String mergedSql;
if (sqlStat != null) {
mergedSql = sqlStat.getSql();
} else {
mergedSql = sql;
}
// ???
Profiler.enter(mergedSql, Profiler.PROFILE_TYPE_SQL);
}
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) {
//更新的countArray 增加更新次数、fetchcount
sqlStat.addUpdateCount(updateCount);
sqlStat.addFetchRowCount(0);
StatFilterContext.getInstance().addUpdateCount(updateCount);
}
}
long millis = nanos / (1000 * 1000);
//慢sql的监控
if (millis >= slowSqlMillis) {
//构建慢的参数
String slowParameters = buildSlowParameters(statement);
//设置参数
sqlStat.setLastSlowParameters(slowParameters);
//最近的执行sql
String lastExecSql = statement.getLastExecuteSql();
if (logSlowSql) {
//慢sql的日志
String msg = "slow sql " + millis + " millis. " + lastExecSql + "" + slowParameters;
switch (slowSqlLogLevel) {
case "WARN":
LOG.warn(msg);
break;
case "INFO":
LOG.info(msg);
break;
case "DEBUG":
LOG.debug(msg);
break;
default:
LOG.error(msg);
}
}
//处理慢sql
handleSlowSql(statement);
}
}
// 获取最近的执行的sql
String sql = statement.getLastExecuteSql();
//执行之后 设置执行时间、错误等等
StatFilterContext.getInstance().executeAfter(sql, nanos, null);
Profiler.release(nanos);
}
额外:mergedSql
//在看前面源码中看到了mergedSql的处理比较有趣,也正是druid的工具类ParameterizedOutputVisitorUtils中比较好的一个功能,进行SQL模板的抽取。下一次就不用对相同的模板的SQL进行相关操作。
public static String parameterize(String sql
, DbType dbType
, SQLSelectListCache selectListCache
, List<Object> outParameters
, SQLParserFeature[] features
, VisitorFeature ...visitorFeatures) {
//根据不同的方言获取sql的语句分析器
SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, dbType, features);
if (selectListCache != null) {
//查询列表缓存有的话 直接设置
parser.setSelectListCache(selectListCache);
}
List<SQLStatement> statementList = parser.parseStatementList();
if (statementList.size() == 0) {
//如果获取到之前的处理过的模版直接返回
return sql;
}
StringBuilder out = new StringBuilder(sql.length());
//Visitor类可以定义遇到某个SQL元素后的处理方法,或者遇到某个SQL元素后的处理方法。OutputVisitor用来把AST(抽象语法树)输出为字符串
//根据不同的方言获取不同的处理方法ParameterizedOutputVisitor用来合并未参数化的SQL进行统计
ParameterizedVisitor visitor = createParameterizedOutputVisitor(out, dbType);
if (outParameters != null) {
//输出参数不为空
visitor.setOutputParameters(outParameters);
}
//配置功能
configVisitorFeatures(visitor, visitorFeatures);
for (int i = 0; i < statementList.size(); i++) {
//sql SQLStatement代表一条SQL,解析器会把一个字符串解析成一个SQL的列表,如果字符串中有多个SQL,每个SQL用分号分隔,会返回一个对应个SQLStatement对象的列表。
SQLStatement stmt = statementList.get(i);
if (i > 0) {
SQLStatement preStmt = statementList.get(i - 1);
if (preStmt.getClass() == stmt.getClass()) {
StringBuilder buf = new StringBuilder();
//获取处理方法
ParameterizedVisitor v1 = createParameterizedOutputVisitor(buf, dbType);
preStmt.accept(v1);
if (out.toString().equals(buf.toString())) {
continue;
}
}
if (!preStmt.isAfterSemi()) {
//拼接分号 分隔不同的sql
out.append(";\n");
} else {
out.append('\n');
}
}
if (stmt.hasBeforeComment()) {
stmt.getBeforeCommentsDirect().clear();
}
Class<?> stmtClass = stmt.getClass();
if (stmtClass == SQLSelectStatement.class) { // only for performance
//查询
SQLSelectStatement selectStatement = (SQLSelectStatement) stmt;
visitor.visit(selectStatement);
visitor.postVisit(selectStatement);
} else {
stmt.accept(visitor);
}
}
//未用原始sql判读
if (visitor.getReplaceCount() == 0
&& parser.getLexer().getCommentCount() == 0
&& sql.charAt(0) != '/') {
boolean notUseOriginalSql = false;
if (visitorFeatures != null) {
for (VisitorFeature visitorFeature : visitorFeatures) {
if (visitorFeature == VisitorFeature.OutputParameterizedZeroReplaceNotUseOriginalSql) {
notUseOriginalSql = true;
}
}
}
if (!notUseOriginalSql) {
int ddlStmtCount = 0;
for (SQLStatement stmt : statementList) {
if (stmt instanceof SQLDDLStatement) {
ddlStmtCount++;
}
}
if (ddlStmtCount == statementList.size()) {
notUseOriginalSql = true;
}
}
if (!notUseOriginalSql) {
return sql;
}
}
return out.toString();
}
总结
今天主要针对StatFilter进行了源码学习,整个过滤器的逻辑处理都是围绕着监控的各个指标参数,也确实印证了druid为监控而生的定义。在看这块代码实现的时候感觉很有趣的就是他的抽象类模版方法这些设计思想,目前项目中也在用,在实际写业务代码的时候也可以尝试去采用架构思想进行设计自己的业务实现。我一直任务并不是一定要接触很牛逼的项目才能提升自己,提升自己的最好方法就是提高自己的定位,在做一个简单的业务功能的时候可以站的怎么让自己的功能高复用、怎么更灵活、怎么能够后续拓展,这个其实就是设计模式的最开始的想法,慢慢总结实践,这样经验一样很宝贵。后面针对Visitor进行细化解读,明确具体sql解析的原理。