skywalking07 - skywalking如何收集Controller的链路

本文涉及的产品
应用实时监控服务-应用监控,每月50GB免费额度
简介: skywalking07 - skywalking如何收集Controller的链路

skywalking07 - skywalking如何收集Controller的链路

对于我们在java中常用的注解@Controller、@RestController,在运行时,将会产生链路,在“链路追踪”中可以进行查看,那么来看看怎么收集的

Instrumentation 指明拦截的类

  • 在org.apache.skywalking.apm.plugin.spring.mvc.v5.define.AbstractControllerInstrumentation抽象类的实现类ControllerInstrumentation、RestControllerInstrumentation中指定了ENHANCE_ANNOTATION分别为org.springframework.stereotype.Controller以及org.springframework.web.bind.annotation.RestController进行指定了增强的类。
public class RestControllerInstrumentation extends AbstractControllerInstrumentation {
    public static final String ENHANCE_ANNOTATION = "org.springframework.web.bind.annotation.RestController";
    @Override
    protected String[] getEnhanceAnnotations() {
        return new String[] {ENHANCE_ANNOTATION};
    }
}
  • 然后通过如下方法指定拦截增强的方法
@Override
    public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
        return new InstanceMethodsInterceptPoint[] {
            new DeclaredInstanceMethodsInterceptPoint() {
                @Override
                public ElementMatcher<MethodDescription> getMethodsMatcher() {
                    return byMethodInheritanceAnnotationMatcher(named("org.springframework.web.bind.annotation.RequestMapping"));
                }
            },
            new DeclaredInstanceMethodsInterceptPoint() {
                @Override
                public ElementMatcher<MethodDescription> getMethodsMatcher() {
                    return byMethodInheritanceAnnotationMatcher(named("org.springframework.web.bind.annotation.GetMapping"))
                        .or(byMethodInheritanceAnnotationMatcher(named("org.springframework.web.bind.annotation.PostMapping")))
                        .or(byMethodInheritanceAnnotationMatcher(named("org.springframework.web.bind.annotation.PutMapping")))
                        .or(byMethodInheritanceAnnotationMatcher(named("org.springframework.web.bind.annotation.DeleteMapping")))
                        .or(byMethodInheritanceAnnotationMatcher(named("org.springframework.web.bind.annotation.PatchMapping")));
                }
            }
        };
    }
  • 同时定义了用来增强该类的增强类org.apache.skywalking.apm.plugin.spring.mvc.v5.ControllerConstructorInterceptor

InstanceConstructorInterceptor 进行类的增强

拦截构造实例的增强,直接对该Controller进行增强。该段代码中的注释比较重要!

/**
 * The <code>ControllerConstructorInterceptor</code> intercepts the Controller's constructor, in order to acquire the
 * mapping annotation, if exist.
 * <p>
 * But, you can see we only use the first mapping value, <B>Why?</B>
 * <p>
 * Right now, we intercept the controller by annotation as you known, so we CAN'T know which uri patten is actually
 * matched. Even we know, that costs a lot.
 * <p>
 * If we want to resolve that, we must intercept the Spring MVC core codes, that is not a good choice for now.
 * <p>
 * Comment by @wu-sheng
 */
public class ControllerConstructorInterceptor implements InstanceConstructorInterceptor {
    @Override
    public void onConstruct(EnhancedInstance objInst, Object[] allArguments) {
        String basePath = "";
        RequestMapping basePathRequestMapping = objInst.getClass().getAnnotation(RequestMapping.class);
        if (basePathRequestMapping != null) {
            if (basePathRequestMapping.value().length > 0) {
                basePath = basePathRequestMapping.value()[0];
            } else if (basePathRequestMapping.path().length > 0) {
                basePath = basePathRequestMapping.path()[0];
            }
        }
        EnhanceRequireObjectCache enhanceRequireObjectCache = new EnhanceRequireObjectCache();
        // PathMappingCache对象会存储方法中方法与URL映射的映射关系
        enhanceRequireObjectCache.setPathMappingCache(new PathMappingCache(basePath));
        objInst.setSkyWalkingDynamicField(enhanceRequireObjectCache);
    }
}
  • Right now, we intercept the controller by annotation as you known, so we CAN’T know which uri patten is actually matched. Even we know, that costs a lot.
    截止目前,我们用注解的方式拦截controller,所以我们不知道匹配的具体的URI。即使我们能够知道,但是那个消耗太大。
  • If we want to resolve that, we must intercept the Spring MVC core codes, that is not a good choice for now.
    如果我们想要去解决这个问题,我们必须拦截Spring MVC 核心代码,但是目前那不是个好选择。

这段话,说明了一个问题,如果有如下代码:

@GetMapping("/test/{pageId}")
    public ApiResult<Boolean> test(@PathVariable("pageId") String pageId) {
        return ApiResult.ok(true);
    }

那么在链路追踪中,看到的链路就是:{GET} XXX/test/{pageId},那么这个pageId并不会使用实际的值,例如“00001”等等,这么做主要考虑了性能的消耗。

不过这么做,对于进行性能分析会带来一定的困扰。尤其是我们工程中,每一个页面都是特殊编辑生成的,每个页面的性能相差巨大,如果仅仅是这样的展示,我们运用链路追踪中的Duration进行排序,则不会按照pageId进行归类了,很是头疼。所以你有解决方案吗?

AbstractMethodInterceptor 进行方法的增强

org.apache.skywalking.apm.plugin.spring.mvc.commons.interceptor.AbstractMethodInterceptor是RequestMappingMethodInterceptor以及RestMappingMethodInterceptor的父类。对方法进行增强,将上一步的映射关系在EnhanceRequireObjectCache中真正形成。并且生成对应的EntrySpan或者LocalSpan。

@Override
    public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
                             MethodInterceptResult result) throws Throwable {
        Boolean forwardRequestFlag = (Boolean) ContextManager.getRuntimeContext().get(FORWARD_REQUEST_FLAG);
        /**
         * Spring MVC plugin do nothing if current request is forward request.
         * Ref: https://github.com/apache/skywalking/pull/1325
         */
        if (forwardRequestFlag != null && forwardRequestFlag) {
            return;
        }
        String operationName;
        if (SpringMVCPluginConfig.Plugin.SpringMVC.USE_QUALIFIED_NAME_AS_ENDPOINT_NAME) {
            operationName = MethodUtil.generateOperationName(method);
        } else {
            EnhanceRequireObjectCache pathMappingCache = (EnhanceRequireObjectCache) objInst.getSkyWalkingDynamicField();
            String requestURL = pathMappingCache.findPathMapping(method);
            if (requestURL == null) {
                requestURL = getRequestURL(method);
                // 添加方法与URL的映射关系
                pathMappingCache.addPathMapping(method, requestURL);
                requestURL = pathMappingCache.findPathMapping(method);
            }
            operationName = getAcceptedMethodTypes(method) + requestURL;
        }
        RequestHolder request = (RequestHolder) ContextManager.getRuntimeContext()
                                                              .get(REQUEST_KEY_IN_RUNTIME_CONTEXT);
        if (request != null) {
            StackDepth stackDepth = (StackDepth) ContextManager.getRuntimeContext().get(CONTROLLER_METHOD_STACK_DEPTH);
            if (stackDepth == null) {
                // 栈为空,为一个新的链路
                ContextCarrier contextCarrier = new ContextCarrier();
                CarrierItem next = contextCarrier.items();
                while (next.hasNext()) {
                    next = next.next();
                    next.setHeadValue(request.getHeader(next.getHeadKey()));
                }
        // 创建一个EntrySpan
                AbstractSpan span = ContextManager.createEntrySpan(operationName, contextCarrier);
                Tags.URL.set(span, request.requestURL());
                Tags.HTTP.METHOD.set(span, request.requestMethod());
                span.setComponent(ComponentsDefine.SPRING_MVC_ANNOTATION);
                SpanLayer.asHttp(span);
                if (SpringMVCPluginConfig.Plugin.SpringMVC.COLLECT_HTTP_PARAMS) {
                    collectHttpParam(request, span);
                }
                if (!CollectionUtil.isEmpty(SpringMVCPluginConfig.Plugin.Http.INCLUDE_HTTP_HEADERS)) {
                    collectHttpHeaders(request, span);
                }
                stackDepth = new StackDepth();
                // 加入一个栈深度
                ContextManager.getRuntimeContext().put(CONTROLLER_METHOD_STACK_DEPTH, stackDepth);
            } else {
                // 本地调用,创建一个LocalSpan
                AbstractSpan span = ContextManager.createLocalSpan(buildOperationName(objInst, method));
                span.setComponent(ComponentsDefine.SPRING_MVC_ANNOTATION);
            }
            stackDepth.increment();
        }
    }

总结

Controller的链路收集,主要分两步,一步是对类的增强,一步是对方法的增强。代码均位于apm-sniffer/apm-sdk-plugin/spring-plugins子工程中。

skywalking收集链路时,使用的URL都是通配符,在链路中,无法针对某个pageId,或者其他通配符的具体的值进行查找。或许skywalking出于性能考虑,但是对于这种不定的通用大接口,的确无法用于针对性的性能分析了。这个问题还需要深入思考寻找,能否在自己项目中,找到一个Balance,只针对自己需要分析性能的接口,按照具体值进行收集,而其他走默认呢?

PS:补充一下版本,SW 8.4版本,新版中已经将agent的部分抽到另一个git中了。

相关实践学习
通过轻量消息队列(原MNS)主题HTTP订阅+ARMS实现自定义数据多渠道告警
本场景将自定义告警信息同时分发至多个通知渠道的需求,例如短信、电子邮件及钉钉群组等。通过采用轻量消息队列(原 MNS)的主题模型的HTTP订阅方式,并结合应用实时监控服务提供的自定义集成能力,使得您能够以简便的配置方式实现上述多渠道同步通知的功能。
目录
相关文章
|
Arthas Java 测试技术
超好用的自带火焰图的 Java 性能分析工具 Async-profiler 了解一下
超好用的自带火焰图的 Java 性能分析工具 Async-profiler 了解一下
2909 0
超好用的自带火焰图的 Java 性能分析工具 Async-profiler 了解一下
|
前端开发 Java API
skywalking番外01 - 如何扩展%tid的logback占位符
skywalking番外01 - 如何扩展%tid的logback占位符
596 0
|
4月前
|
存储 监控 Shell
SkyWalking微服务监控部署与优化全攻略
综上所述,虽然SkyWalking的初始部署流程相对复杂,但通过一步步的准备和配置,可以充分发挥其作为可观测平台的强大功能,实现对微服务架构的高效监控和治理。尽管未亲临,心已向往。将一件事做到极致,便是天分的展现。
|
11月前
|
数据建模 应用服务中间件 nginx
docker替换宿主与容器的映射端口和文件路径
通过正确配置 Docker 的端口和文件路径映射,可以有效地管理容器化应用程序,确保其高效运行和数据持久性。在生产环境中,动态替换映射配置有助于灵活应对各种需求变化。以上方法和步骤提供了一种可靠且易于操作的方案,帮助您轻松管理 Docker 容器的端口和路径映射。
757 3
|
缓存 网络协议 前端开发
Web 性能优化|了解 HTTP 协议后才能理解的预加载
本文旨在探讨和分享多种预加载技术及其在提升网站性能、优化用户体验方面的应用。
|
消息中间件 存储 监控
RocketMQ Tag 详解!
本文详细介绍了 RocketMQ 中 Tag 的原理及其应用场景。Tag 是一种消息过滤机制,允许生产者在发送消息时指定标签,消费者据此选择性消费。文章通过源码分析展示了 Tag 在消息发送、存储及消费阶段的作用,并提供了完整的示例代码。尽管 Tag 功能简单高效,但也存在单一维度过滤等局限性。适合需要高效、低延迟消息传递的场景,如日志监控、电商系统等。
1426 2
|
SQL Oracle 关系型数据库
SQL与PL/SQL:数据库编程语言的比较
【8月更文挑战第31天】
448 1
|
搜索推荐 Linux Android开发
深入解析安卓与iOS系统架构设计差异
本文旨在探讨Android和iOS两大主流操作系统在架构设计上的根本差异。通过分析两种系统的设计理念、核心组件以及实际应用表现,揭示它们如何反映不同的开发哲学和用户体验策略。我们将从系统层级结构、内存管理机制、用户界面设计三个方面入手,逐一对比Android的开放性和灵活性如何与其对手iOS的封闭性和一致性相互辉映。
|
缓存
计算机网络——数据链路层-可靠传输的实现机制:选择重传协议SR(介绍、工作原理、窗口尺寸、题目练习)
计算机网络——数据链路层-可靠传输的实现机制:选择重传协议SR(介绍、工作原理、窗口尺寸、题目练习)
1033 1
|
机器学习/深度学习 算法 物联网
LoRA及其变体概述:LoRA, DoRA, AdaLoRA, Delta-LoRA
LoRA可以说是针对特定任务高效训练大型语言模型的重大突破。它被广泛应用于许多应用中。在本文中,我们将解释LoRA本身的基本概念,然后介绍一些以不同的方式改进LoRA的功能的变体,包括LoRA+、VeRA、LoRA- fa、LoRA-drop、AdaLoRA、DoRA和Delta-LoRA。
1231 2