需求
通常情况下,如果我们没有系统内部的调用情况,比如我们这里重点聚焦的Service层的接口性能指标 ,比如 调用次数、Avg执行时间、Min执行时间、Max执行时间、成功次数、失败次数、慢执行次数等等,以及根据监控结果触发某些告警等等 ,上述指标都是没有办法很灵活的采集到的
采集方案
我们先来讨论下实现上述需求的方案
- 硬编码
- AOP
- JavaAgent 字节码插桩
那如何做到更灵活的实现代码零侵入的实现Service层的接口监控呢?
OK ,直奔主题 。方案必然是第三种,使用字节码插桩实现Service的零侵入监控
采集目标
我们需要对哪些对象插桩呢?
@Service注解 标注的类吗? 这里犯了一个致命的错误,如果想要做这种底层的基础组件,不要对用户的使用场景做设定 ,方案要更具有通用性
我们更倾向于让用户自主配置监控的 include 与 exclude .
我们不知道统计哪个类,也不知道统计哪个方法 ,一切都是基于用户自主的配置
模型设计
核心: 使用JavaAgent获取到用户配置的数据, 匹配(排除)后 使用javassist来修改字节码,进行插桩 ,插入我们的监控逻辑。
那我们都需要监控哪些指标呢?
开始时间、用时、异常消息、异常类型、服务类名、方法名 ,当然了都是可以扩展的比如我们可以增加主机IP、应用名称、标识追踪ID等等
简单起见,我们先不引入过多的字段。
Code
我们先聚焦到Javassit修改字节码,不要和JavaAgent掺和在一起,先实现第一步
public class ArtisanServiceCollect extends AbstractCollect {
private String targetPackage;
public ArtisanServiceCollect(String target_package) {
this.targetPackage = target_package;
}
public void transform(Instrumentation instrumentation) {
instrumentation.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
if (className == null) {
return null;
}
if (!className.startsWith(targetPackage.replaceAll("\\.", "/"))) {
return null;
}
try {
return buildCtClass(loader, className.
replaceAll("/", ".")).toBytecode();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
});
}
public CtClass buildCtClass(ClassLoader loader, String className) throws NotFoundException {
ClassPool pool = new ClassPool();
pool.insertClassPath(new LoaderClassPath(loader));
CtClass ctClass = pool.get(className);
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;
}
try {
buildMethod(ctClass, m);
} catch (Exception e) {
e.printStackTrace();
}
}
return ctClass;
}
public void buildMethod(CtClass ctClass, CtMethod oldMethod) throws Exception {
// copy 一个方法
// 修改源方法名称 $agent
// 原方法中 插入模板代码
CtMethod newMethod = CtNewMethod.copy(oldMethod, ctClass, null);
oldMethod.setName(oldMethod.getName() + "$agent");
String beginSrc = String.
format("Object stat=com.artisan.agent.collect.ServiceCollect.begin(\"%s\",\"%s\");", ctClass.getName(), oldMethod.getName());
String errorSrc = "com.artisan.agent.collect.ServiceCollect.error(e,stat);";
String endSrc = "com.artisan.agent.collect.ServiceCollect.end(stat);";
String template = oldMethod.getReturnType().getName().equals("void") ? voidSource : source;
newMethod.setBody(String.format(template, beginSrc, newMethod.getName(), errorSrc, endSrc));
ctClass.addMethod(newMethod);
}
public static ServiceStatistics begin(String className, String methodName) {
ServiceStatistics bean = new ServiceStatistics();
bean.setBeginTime(System.currentTimeMillis());
bean.setServiceName(className);
bean.setMethodName(methodName);
bean.setModelType("service");
System.out.println(JSON.toJSONString(bean));
return bean;
}
public static void error(Throwable e, Object obj) {
ServiceStatistics bean = (ServiceStatistics) obj;
bean.setErrorType(e.getClass().getSimpleName());
bean.setErrorMsg(e.getMessage());
}
public static void end(Object obj) {
ServiceStatistics bean = (ServiceStatistics) obj;
bean.setUseTime(System.currentTimeMillis() - bean.getBeginTime());
System.out.println(JSON.toJSONString(obj));
}
// Object obj= begin (className,methodName)
// error(err,obj)
// end(obj)
final static String source = "{\n"
+ "%s"
+ " Object result=null;\n"
+ " try {\n"
+ " result=($w)%s$agent($$);\n"
+ " } catch (Throwable e) {\n"
+ "%s"
+ " throw e;\n"
+ " }finally{\n"
+ "%s"
+ " }\n"
+ " return ($r) result;\n"
+ "}\n";
final static String voidSource = "{\n"
+ "%s"
+ " try {\n"
+ " %s$agent($$);\n"
+ " } catch (Throwable e) {\n"
+ "%s"
+ " throw e;\n"
+ " }finally{\n"
+ "%s"
+ " }\n"
+ "}\n";
}
写个单元测试测试一下javassit是否生效。
当然了仅仅有这个javassit是无法直接运行的,我们还要依靠javaagent来实现对类的拦截
本文标题:APM - 零侵入监控Service服务
本文链接:https://blog.quwenai.cn/post/6768.html
版权声明:本文不使用任何协议授权,您可以任何形式自由转载或使用。







还没有评论,来说两句吧...