作者奇佐(企业代号名),目前负责贝壳找房java后端开发工作。
1 导读
SkyWalking中Java探针是使用JavaAgent的两大字节码操作工具之一的Byte Buddy(另外是Javassist)实现的。项目还包含.Net core和Nodejs自动探针,以及Service Mesh Istio的监控。总体上,SkyWalking是一个多语言,多场景的适配,特别为微服务、云原生和基于容器架构设计的可观测性分析平台(Observability Analysis Platform)。
本文基于SkyWalking 5.0.0-RC2和Byte Buddy 1.7.9版本,会从以下几个章节,让大家掌握SkyWalking Java探针的使用,进而让SkyWalking在自己公司中的二次开发变得触手可及。
Byte Buddy实现JavaAgent项目
迭代JavaAgent项目的方法论
SkyWalking agent项目如何Debug
SkyWalking插件开发实践
文章底部有SkyWalking和Byte Buddy相应的学习资源。
2 Byte Buddy实现
首先如果你对JavaAgent还不是很了解可以先百度一下,或在公众号内看下 《JavaAgent原理与实践》 简单入门下。
SpringMVC分发请求的关键方法相信已经不用我在赘述了,那我们来编写Byte Buddy JavaAgent代码吧。
1 public class AgentMain { 2 public static void premain(String agentOps, Instrumentation instrumentation) { 3 new AgentBuilder.Default() 4 .type(ElementMatchers.named("org.springframework.web.servlet.DispatcherServlet")) 5 .transform((builder, type, classLoader, module) -> 6 builder.method(ElementMatchers.named("doDispatch")) 7 .intercept(MethodDelegation.to(DoDispatchInterceptor.class))) 8 .installOn(instrumentation); 9 } 10}
编写DispatcherServlet doDispatch拦截器代码(是不是跟AOP如出一辙)
1 public class DoDispatchInterceptor { 2 @RuntimeType 3 public static Object intercept(@Argument(0) HttpServletRequest request, @SuperCall Callable<?> callable) { 4 final StringBuilder in = new StringBuilder(); 5 if (request.getParameterMap() != null && request.getParameterMap().size() > 0) { 6 request.getParameterMap().keySet().forEach(key -> in.append("key=" + key + "_value=" + request.getParameter(key) + ",")); 7 } 8 long agentStart = System.currentTimeMillis(); 9 try { 10 return callable.call(); 11 } catch (Exception e) { 12 System.out.println("Exception :" + e.getMessage()); 13 return null; 14 } finally { 15 System.out.println("path:" + request.getRequestURI() + " 入参:" + in + " 耗时:" + (System.currentTimeMillis() - agentStart)); 16 } 17 } 18}
resources/META-INF/MANIFEST.MF
1Manifest-Version: 1.0 2Premain-Class: com.z.test.agent.AgentMain 3Can-Redefine-Classes: true
pom.xml文件
1dependencies 2 +net.bytebuddy.byte-buddy 3 +javax.servlet.javax.servlet-api *scope=provided 4plugins 5 +maven-jar-plugin *manifestFile=src/main/resources/META-INF/MANIFEST.MF 6 +maven-shade-plugin *include:net.bytebuddy:byte-buddy:jar: 7 +maven-compiler-plugin
小结:没几十行代码就完成了,通过Byte Buddy实现应用组件SpringMVC记录请求路径、入参、执行时间JavaAgent项目,是不是觉得自己很优秀。
3 持续迭代JavaAgent
本章节主要介绍JavaAgent如何Debug,以及持续集成的方法论。
首先我的JavaAgent项目目录结构如图所示:
用项目是用几行代码实现的SpringBootWeb项目:
1@SpringBootApplication(scanBasePackages = {"com"}) 2public class TestBootWeb { 3 public static void main(String[] args) { 4 SpringApplication.run(TestBootWeb.class, args); 5 } 6 @RestController 7 public class ApiController { 8 @PostMapping("/ping") 9 public String ping(HttpServletRequest request) { 10 return "pong"; 11 } 12 } 13}
下面是关键JavaAgent项目如何持续迭代与集成:
1VM options增加:-JavaAgent:{$HOME}/Code/github/z_my_test/test-agent/target/test-agent-1.0-SNAPSHOT.jar=args 2Before launch 在Build之前增加: 3 Working directory:{$HOME}/Code/github/incubator-skywalking 4 Command line:-T 1C -pl test-agent -am clean package -Denforcer.skip=true -Dmaven.test.skip=true -Dmaven.compile.fork=true
小结:看到这里的将JavaAgent持续迭代集成方法,是不是瞬间觉得自己手心已经发痒起来,很想编写一个自己的agent项目了呢,等等还有一个好消息:test-demo这10几行的代码实现的Web服务,居然有5k左右的类可以使用agent增强。
注意mvn编译加速的命令是maven3+版本以上才支持的哈。
4 SkyWalking Debug
峰回路转,到了文章的主题《SkyWalking之高级用法》的正文啦。首先,JavaAgent项目想Debug,还需要将agent代码与接入agent项目至少在同一个工作空间内,网上方法有很多,这里我推荐大家一个最简单的方法。File->New->Module from Exisiting Sources…引入skywalking-agent源码即可
详细的idea编辑器配置:
优化SkyWalking agent编译时间,我的集成时间优化到30秒左右:
1VM options增加:-JavaAgent:-JavaAgent:{$HOME}/Code/github/incubator-skywalking/skywalking-agent/skywalking-agent.jar:不要用dist里面的skywalking-agent.jar,具体原因大家可以看看源码:apm-sniffer/apm-agent/pom.xml中的maven插件的使用。 2Before launch 在Build之前增加: 3 Working directory:{$HOME}/Code/github/incubator-skywalking 4 Command line:-T 1C -pl apm-sniffer/apm-sdk-plugin -amd clean package -Denforcer.skip=true -Dmaven.test.skip=true -Dmaven.compile.fork=true: 这里我针对插件包,因为紧接着下文要开发插件 5另外根pom注释maven-checkstyle-plugin也可加速编译
5 kob之SkyWalking插件编写
kob(贝壳分布式作业调度框架)是贝壳找房项目微服务集群中的基础组件,通过编写贝壳分布式作业调度框架的SkyWalking插件,可以实时收集作业调度任务的执行链路信息,从而及时得到基础组件的稳定性,了解细节可点击阅读《 贝壳分布式调度框架简介 》。想详细了解SkyWalking插件编写可在文章底部参考链接中,跳转至对应的官方资源,好话不多说,代码一把唆起来。
apm-sdk-plugin pom.xml增加自己的插件model
1<artifactId>apm-sdk-plugin</artifactId> 2 <modules> 3 <module>kob-plugin</module> 4 ... 5 <modules>
resources.skywalking-plugin.def增加自己的描述
1kob=org.apache.skywalking.apm.plugin.kob.KobInstrumentation
在SkyWalking的项目中,通过继承ClassInstanceMethodsEnhancePluginDefine可以定义需要拦截的类和增强的方法,编写作业调度方法的instrumentation
1public class KobInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { 2 private static final String ENHANCE_CLASS = "com.ke.kob.client.spring.core.TaskDispatcher"; 3 private static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.kob.KobInterceptor"; 4 @Override 5 protected ClassMatch enhanceClass() { 6 return NameMatch.byName(ENHANCE_CLASS); 7 } 8 @Override 9 protected ConstructorInterceptPoint[] getConstructorsInterceptPoints() { 10 return null; 11 } 12 @Override 13 protected InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { 14 return new InstanceMethodsInterceptPoint[] { 15 new InstanceMethodsInterceptPoint() { 16 @Override 17 public ElementMatcher<MethodDescription> getMethodsMatcher() { 18 return named("dispatcher1"); 19 } 20 @Override 21 public String getMethodsInterceptor() { 22 return INTERCEPT_CLASS; 23 } 24 @Override 25 public boolean isOverrideArgs() { 26 return false; 27 } 28 } 29 }; 30 } 31}
通过实现InstanceMethodsAroundInterceptor后,定义beforeMethod、afterMethod和handleMethodException的实现方法,可以环绕增强指定目标方法,下面自定义interceptor实现span的跟踪(这里需要注意SkyWalking中span的生命周期,在afterMethod方法中结束span)
1public class KobInterceptor implements InstanceMethodsAroundInterceptor { 2 @Override 3 public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, MethodInterceptResult result) throws Throwable { 4 final ContextCarrier contextCarrier = new ContextCarrier(); 5 com.ke.kob.client.spring.model.TaskContext context = (TaskContext) allArguments[0]; 6 CarrierItem next = contextCarrier.items(); 7 while (next.hasNext()) { 8 next = next.next(); 9 next.setHeadValue(JSON.toJSONString(context.getUserParam())); 10 } 11 AbstractSpan span = ContextManager.createEntrySpan("client:"+allArguments[1]+",task:"+context.getTaskKey(), contextCarrier); 12 span.setComponent(ComponentsDefine.TRANSPORT_CLIENT); 13 SpanLayer.asRPCFramework(span); 14 } 15 @Override 16 public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Object ret) throws Throwable { 17 ContextManager.stopSpan(); 18 return ret; 19 } 20 @Override 21 public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Throwable t) { 22 } 23}
实现效果,将操作名改成任务执行节点+任务执行方法,实现kob的SkyWalking的插件编写,加上报警体系,可以进一步增加公司基础组件的稳定性。
6 参考链接
https://github.com/apache/incubator-skywalking
https://github.com/raphw/byte-buddy
作 者:奇佐(企业代号名)
出品人:维托、叶离(企业代号名)
---------- END ----------
JavaAgent原理与实践
贝壳分布式调度框架简介