zipkin brave mysql_Zipkin原理学习--日志追踪 MySQL 执行语句

本文介绍了如何使用Zipkin Brave插件进行MySQL执行语句的日志追踪,包括配置示例、原理解析,并探讨了对其他数据库的通用性解决方案,如MyBatis拦截器和Druid过滤器。
摘要由CSDN通过智能技术生成

目前Zipkin官方提供了插件用于支持对MySQL语句执行过程的日志追踪,提供了对MySQL5、MySQL6和MySQL8的支持,官方地址:https://github.com/openzipkin/brave/tree/master/instrumentation

一、介绍及示例

配置示例:

1、引入相关jar包:

io.zipkin.brave

brave-instrumentation-mysql

5.4.3

2、在url中添加拦截器和服务名:statementInterceptors=brave.mysql.TracingStatementInterceptor&zipkinServiceName=myDatabaseService

public void mysql() throws Exception{

Class.forName("com.mysql.jdbc.Driver");

System.out.println("成功加载驱动");

Connection connection = null;

Statement statement = null;

ResultSet resultSet = null;

try {

String url = "jdbc:mysql://localhost:3306/db?user=root&password=root&useUnicode=true&characterEncoding=UTF8&statementInterceptors=brave.mysql.TracingStatementInterceptor&zipkinServiceName=myDatabaseService";

connection = DriverManager.getConnection(url);

System.out.println("成功获取连接");

statement = connection.createStatement();

String sql = "select * from tbl_user";

resultSet = statement.executeQuery(sql);

resultSet.beforeFirst();

while (resultSet.next()) {

System.out.println(resultSet.getString(1));

}

System.out.println("成功操作数据库");

} catch(Throwable t) {

// TODO 处理异常

t.printStackTrace();

} finally {

if (resultSet != null) {

resultSet.close();

}

if (statement != null) {

statement.close();

}

if (connection != null) {

connection.close();

}

System.out.println("成功关闭资源");

}

}

3、zipserver中结果示例:

二、实现原理

其实现原理也还是挺容易理解的,利用MySQL JDBC提供的拦截器机制,在sql语句执行前新建一个span调用,在sql语句执行后结束span调用,记录整个调用过程的耗时及sql语句信息。

public class TracingStatementInterceptor implements StatementInterceptorV2 {

/**

* Uses {@link ThreadLocalSpan} as there‘s no attribute namespace shared between callbacks, but

* all callbacks happen on the same thread.

*

*

Uses {@link ThreadLocalSpan#CURRENT_TRACER} and this interceptor initializes before

* tracing.

*/

@Override

public ResultSetInternalMethods preProcess(String sql, Statement interceptedStatement,

Connection connection) {

// Gets the next span (and places it in scope) so code between here and postProcess can read it

//新生成一个Span

Span span = ThreadLocalSpan.CURRENT_TRACER.next();

if (span == null || span.isNoop()) return null;

// When running a prepared statement, sql will be null and we must fetch the sql from the statement itself

if (interceptedStatement instanceof PreparedStatement) {

sql = ((PreparedStatement) interceptedStatement).getPreparedSql();

}

int spaceIndex = sql.indexOf(‘ ‘); // Allow span names of single-word statements like COMMIT

span.kind(Span.Kind.CLIENT).name(spaceIndex == -1 ? sql : sql.substring(0, spaceIndex));

span.tag("sql.query", sql);

parseServerIpAndPort(connection, span);

//记录启动时间

span.start();

return null;

}

@Override

public ResultSetInternalMethods postProcess(String sql, Statement interceptedStatement,

ResultSetInternalMethods originalResultSet, Connection connection, int warningCount,

boolean noIndexUsed, boolean noGoodIndexUsed, SQLException statementException) {

Span span = ThreadLocalSpan.CURRENT_TRACER.remove();

if (span == null || span.isNoop()) return null;

if (statementException != null) {

span.tag("error", Integer.toString(statementException.getErrorCode()));

}

//记录服务停止时间

span.finish();

return null;

}

/**

* MySQL exposes the host connecting to, but not the port. This attempts to get the port from the

* JDBC URL. Ex. 5555 from {@code jdbc:mysql://localhost:5555/database}, or 3306 if absent.

*/

static void parseServerIpAndPort(Connection connection, Span span) {

try {

URI url = URI.create(connection.getMetaData().getURL().substring(5)); // strip "jdbc:"

String remoteServiceName = connection.getProperties().getProperty("zipkinServiceName");

if (remoteServiceName == null || "".equals(remoteServiceName)) {

String databaseName = connection.getCatalog();

if (databaseName != null && !databaseName.isEmpty()) {

remoteServiceName = "mysql-" + databaseName;

} else {

remoteServiceName = "mysql";

}

}

//添加服务名

span.remoteServiceName(remoteServiceName);

String host = connection.getHost();

if (host != null) {

span.remoteIpAndPort(host, url.getPort() == -1 ? 3306 : url.getPort());

}

} catch (Exception e) {

// remote address is optional

}

}

@Override public boolean executeTopLevelOnly() {

return true; // True means that we don‘t get notified about queries that other interceptors issue

}

@Override public void init(Connection conn, Properties props) {

// Don‘t care

}

@Override public void destroy() {

// Don‘t care

}

}

思考:其实Zipkin官方给出的这种方案还是能给我们一些启发的,目前对于数据库官方只支持了mysql,对于Postgresql、Oracle 和 SQL Server 等可以基于技术方案有以下两种局限解决方案:

(1)利用mybatis的拦截器机制来实现,和上面的实现类似

(2)利用数据库池 Druid的过滤器同样可以实现。

以上两种方案的好处:对于数据库通用支持。

————————————————

原文链接:https://blog.csdn.net/qq924862077/article/details/88559499

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值