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 在自己公司中的二次开发变得触手可及。
文章底部有 SkyWalking 和 Byte Buddy 相应的学习资源。
2Byte 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+版本以上才支持的哈。
4SkyWalking 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也可加速编译
复制代码
5kob 之 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 的插件编写,加上报警体系,可以进一步增加公司基础组件的稳定性。
作者介绍:
奇佐(企业代号名),目前负责贝壳找房 java 后端开发工作。
本文转载自公众号贝壳产品技术(ID:gh_9afeb423f390)。
原文链接:
https://mp.weixin.qq.com/s/6tRxWl_XYqDeIAx2fp4S5Q
评论