天天看點

skywalking07 - skywalking如何收集Controller的鍊路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中了。

繼續閱讀