一点修改和准备工作
- 在tomcat测试项目中添加jedis相关代码(需要本地开一个redis),代码省略
- 提取Enhancer公共部分
public abstract class PluginEnhancer {
public abstract String getEnhanceClassName();
public abstract String getInterceptorClassName();
public abstract ElementMatcher getInstanceMethodInterceptorMatch();
public DynamicType.Builder<?> doIntercept(DynamicType.Builder<?> builder){
try {
ClassInstanceMethodInterceptor classInstanceMethodInterceptor =(ClassInstanceMethodInterceptor) Class.forName(getInterceptorClassName()).newInstance();
builder = builder.method(not(isStatic()).and(getInstanceMethodInterceptorMatch())).intercept(MethodDelegation.to(classInstanceMethodInterceptor));
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return builder;
}
}
具体Enhancer只做少量工作
* 获取要拦截的类
* 获取要拦截的函数
* 获取Interceptor类名
public class TomcatEnhancer extends PluginEnhancer {
public static String ENHANCE_CLASSNAME = "org.apache.catalina.core.StandardWrapperValve";
public static String INTERCEPTOR_CLASSNAME = "younian.apmsample.agent.plugin.tomcat.TomcatInterceptor";
@Override
public String getEnhanceClassName() {
return ENHANCE_CLASSNAME;
}
@Override
public String getInterceptorClassName() {
return INTERCEPTOR_CLASSNAME;
}
@Override
public ElementMatcher getInstanceMethodInterceptorMatch() {
return named("invoke");
}
}
运行一下tomcat:
TomcatInterceptor before...
服务正在运行: PONG
JedisInterceptor before..
JedisInterceptor after..
JedisInterceptor before..
JedisInterceptor after..
value of key:1519712852893
TomcatInterceptor after...
现在tomcat以及jedis都能够拦截到了。阶段Tag:添加jedis插件
详细的拦截信息
现在拦截到的信息从控制台来看基本是无意义且混乱的,那我们要如何处理呢?
- 请求链从tomcat转到jedis,如果构成一条链?
如何将当前上下文中的信息进行临时存储,并保证线程安全呢?这一点可以借助ThreadLocal来完成,发起创建Trace信息时,往ThreadLocal中写入记录,当前请求过程中再发起新的请求时,从ThreadLocal中获取Trace信息继续往下传递,等信息可以提交归档的时候,从ThreadLocal读取,并清除ThreadLocal中的信息。
- 组件请求如何开始结束?--比如jedis get操作。
一个组件的一个操作定义为一个Span,比如一个jedis.ping(),一个jedis.get(),Span是调用链中的最小单元节点。我们定义一个Span,每个span有自己的id,另外包含一个parentSpanId,标识上级spanid,如果是tomcat没有上级span我们可以定义parentSpanId=0;这样一条链就串起来了。
public class Span {
private int spanId;
private int parentSpanId;
private long startTime;
private long endTime;
private String operationName;
}
这里有一点需要说明:比如tomcat测试例子中的操作,用户请求“/”,然后在Controller中有jedis.ping()、jedis.set()、jedis.get()这四个操作我们分别设置id是1 2 3 4 的话,那么
* 1的parent是0
* 2的parent是1
* 但是3的parent不是2,而是1,因为2和3是并列操作,没有父子关系,没有2也可以有3。
所以我们使用栈的数据结构来对span进行存储,正确流程是:1入栈->2入栈->2出栈->3入栈->3出栈->4入栈->4出栈->1出栈。知道栈空了为止说明这一条链已经结束了。
操作
- 定义ContextManager用于Interceptor操作Span以及ThreadLocal变量保存。
public class ContextManager {
private final static ThreadLocal<TraceContext> CONTEXT = new ThreadLocal<>();
public static Span createSpan(String operationName){
return get().createSpan(operationName);
}
public static Span stopSpan(){
return get().stopSpan();
}
public static TraceContext get(){
return CONTEXT.get();
}
}
- 定义TraceContext作为Span的栈容器。
public class TraceContext {
private static LinkedList<Span> activeSpanStak = new LinkedList<>();
private static int spanIdGenerator;
static Span createSpan(String operationName) {
Span parentSpan = peek();
Span span = null;
if (parentSpan == null) {
span = new Span(spanIdGenerator++, System.currentTimeMillis(), operationName);
} else {
span = new Span(spanIdGenerator++, parentSpan.getSpanId(), System.currentTimeMillis(), operationName);
}
TraceContext.push(span);
return span;
}
static Span stopSpan() {
Span span = pop();
span.setEndTime(System.currentTimeMillis());
return span;
}
static void push(Span span) {
activeSpanStak.push(span);
}
static Span pop() {
return activeSpanStak.pop();
}
static Span peek() {
return activeSpanStak.peek();
}
}
- 运行tomcat查看控制台:
服务正在运行: PONG
Span{spanId=10, parentSpanId=9, startTime=1519716542175, endTime=1519716542176, operationName='set'}
Span{spanId=11, parentSpanId=9, startTime=1519716542176, endTime=1519716542176, operationName='get'}
value of key:1519716542175
Span{spanId=9, parentSpanId=0, startTime=1519716542174, endTime=1519716542177, operationName='invoke'}
可以看到多个span的信息已经展示出来。可以看到span的起止时间以及操作,以及父span。
阶段Tag:线程内链的构造
附
另外,如果想要将agent放到tomcat的pom文件中,就会出现 http://www.younian.me/archives/%E6%96%B0%E7%89%88tomcat%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6%E6%94%B9%E5%8F%98%E5%AF%BC%E8%87%B4%E7%9A%84%E4%B8%8D%E5%90%8C%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E5%8A%A0%E8%BD%BD%E5%90%8C%E4%B8%80%E4%B8%AA%E7%B1%BB%E7%9A%84%E5%AE%9E%E4%BE%8B%E8%B5%8B%E5%80%BC%E5%87%BA%E9%94%99
这里出现的问题。看来依然避免不了,有时间可以研究一下。