分布式调用链(二)---Control/Service层插桩埋点实际处理
程序员文章站
2022-06-18 11:19:37
...
为什么要在Control/Service 层进行埋点
因为请求过来以后,先进入Control,在进入Service。
在Control:可以统计某个URL,具体的执行次数、时间、一段时间的流量统计。
在Service :可以统计某个方法的用时。
当有了数据以后,可以使用kibana来做可视化工具。kibana是属于elasticsearch的一款工具。
采集端执行流程
应用系统嵌入监听器,采集到数据以后,由http协议发送给集群,
在发送给elasticsearch.(也可以直接进入DB,但是不灵活),
elasticsearch好处: 当遇到异常信息以后,可以用其反向搜索引擎,
采集流程
- 判定谁是采集目标
- 构建插桩后的Class字节
- 采集方法运行时信息,如开始时间、结束时间、方法名
- 上传运行时信息
怎么知道某个类是需要采集的类?
怎么知道是Control还是service
根据UML图,可以将共同点抽象出来,如所有类都要判定是否是目标,都要加载到ClassLoader。所有抽象出两个接口:
但不是所有采集器不一定要记录开始信息、结束信息、异常信息、统计上传信息,只是一般情况会。所以可以抽象出一个抽象类。AbstractCollects。在上传信息的时候,需要用到线程,自定义一个threadService.
总之做设计时候,必须用到的用接口,通用的用抽象类。接口是用来简化编程,屏蔽底层实现。
采集器需要一个入口,定义AgentMain
具体定义采集器,由于需要采集Controler、service、jdbc层的信息,定义3个采集器。如果用到Dubbo,可以继续定义。
将采集器注册到AgentMain中,由于采集器不多,后期扩展欲望不是很强,可以采取硬编码方式,也可以写个xml文件进行配置,方便插拔。
Service层的采集
//判定是否是service层代码
public boolean isTarget(String className, ClassLoader loader, CtClass ctclass) {
try {
//获取class类的注解,然后遍历。如果注解是@service,则命中。
for (Object obj : ctclass.getAnnotations()) {
if (obj.toString().startsWith("@org.springframework.stereotype.Service")) {
return true;
}
}
} catch (ClassNotFoundException e) {
System.err.println(String.format("bit apm run error targetClassName=%s errorMessage=%s",className,e.getClass().getSimpleName()+":"+e.getMessage()));
}
return false;
}
public byte[] transform(ClassLoader loader, String className, byte[] classfileBuffer, CtClass ctclass) throws
Exception {
AgentLoader byteLoade = new AgentLoader(className, loader, ctclass);
CtMethod[] methods = ctclass.getDeclaredMethods();
for (CtMethod m : methods) {
//排除写法,比火箭写法好,可读性更高。火箭写法作者逻辑清楚,但不利于可读性。
// 屏蔽非公共方法
if (!Modifier.isPublic(m.getModifiers())) {
continue;
}
// 屏蔽静态方法
if (Modifier.isStatic(m.getModifiers())) {
continue;
}
// 屏蔽本地方法
if (Modifier.isNative(m.getModifiers())) {
continue;
}
AgentLoader.MethodSrcBuild build = new AgentLoader.MethodSrcBuild();
build.setBeginSrc(String.format(beginSrc,className,m.getName()));
build.setEndSrc(endSrc);
build.setErrorSrc(errorSrc);
byteLoade.updateMethod(m, build);
}
return byteLoade.toBytecote();
}
由于封装的时候,涉及到执行前,执行后,与出异常3种情况,所以定义三个方法。