美文网首页
skywalking简单原理

skywalking简单原理

作者: kakukeme | 来源:发表于2021-03-24 16:56 被阅读0次

    skywalking原理

    devops转载链接

    简要介绍skywalking的实现原理

    基本原理

    我理解的skywalking是通过javaagent+bytebuddy+plugins方式实现的。

    javaagent

    skywalking通过maven-shade-plugin打包,具体配置如下

    <plugin>
        <artifactId>maven-shade-plugin</artifactId>
        <executions>
            <execution>
                <phase>package</phase>
                <goals>
                    <goal>shade</goal>
                </goals>
                <configuration>
                    <shadedArtifactAttached>false</shadedArtifactAttached>
                    <createDependencyReducedPom>true</createDependencyReducedPom>
                    <createSourcesJar>true</createSourcesJar>
                    <shadeSourcesContent>true</shadeSourcesContent>
                    <transformers>
                        <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                            <manifestEntries>
                                <Premain-Class>${premain.class}</Premain-Class>
                                <Can-Redefine-Classes>${can.redefine.classes}</Can-Redefine-Classes>
                                <Can-Retransform-Classes>${can.retransform.classes}</Can-Retransform-Classes>
                            </manifestEntries>
                        </transformer>
                    </transformers>
                    <artifactSet>
                        <excludes>
                            <exclude>*:gson</exclude>
                            <exclude>io.grpc:*</exclude>
                            <exclude>io.netty:*</exclude>
                            <exclude>io.opencensus:*</exclude>
                            <exclude>com.google.*:*</exclude>
                            <exclude>com.google.guava:guava</exclude>
                        </excludes>
                    </artifactSet>
                    <relocations>
                        <relocation>
                            <pattern>${shade.net.bytebuddy.source}</pattern>
                            <shadedPattern>${shade.net.bytebuddy.target}</shadedPattern>
                        </relocation>
                    </relocations>
                    <filters>
                        <filter>
                            <artifact>net.bytebuddy:byte-buddy</artifact>
                            <excludes>
                                <exclude>META-INF/versions/9/module-info.class</exclude>
                            </excludes>
                        </filter>
                    </filters>
                </configuration>
            </execution>
        </executions>
    </plugin>
    

    maven-shade-plugin链接

    ByteBuddy

    ByteBuddy官网
    ByteBuddy 字节码生成工具,
    通过BuyteBuddy在代码执行动作时植入作者想做的事情。

    plugin

    skywalking适配了业界大部分主流的中间件,主要是通过apm-sniffer module下的apm-sdk-plugin来做这件事,这个module也是更新最频繁的,不断适配新的组件。

    c6b2dbc33306e6945f7cc5a0f9c5215d.png

    基本源码解读

    入口

    首先我们看到maven-shade-plugin配置的Premain-Class,

    <premain.class>org.apache.skywalking.apm.agent.SkyWalkingAgent</premain.class>
    

    SkyWalkingAgent的premain方法主要做了以下几件事

    public static void premain(String agentArgs, Instrumentation instrumentation) throws PluginException, IOException {
            final PluginFinder pluginFinder;
            try {
                // 配置相关config,包括通过java javaagent一些启动参数
                SnifferConfigInitializer.initialize(agentArgs);
    
                // 加载所有plugin,即apm-sdk-plugin module
                pluginFinder = new PluginFinder(new PluginBootstrap().loadPlugins());
    
            } catch (ConfigNotFoundException ce) {
                logger.error(ce, "SkyWalking agent could not find config. Shutting down.");
                return;
            } catch (AgentPackageNotFoundException ape) {
                logger.error(ape, "Locate agent.jar failure. Shutting down.");
                return;
            } catch (Exception e) {
                logger.error(e, "SkyWalking agent initialized failure. Shutting down.");
                return;
            }
    
            // 生成ByteBuddy
            final ByteBuddy byteBuddy = new ByteBuddy()
                .with(TypeValidation.of(Config.Agent.IS_OPEN_DEBUGGING_CLASS));
    
            AgentBuilder agentBuilder = new AgentBuilder.Default(byteBuddy)
                .ignore(
                    nameStartsWith("net.bytebuddy.")
                        .or(nameStartsWith("org.slf4j."))
                        .or(nameStartsWith("org.groovy."))
                        .or(nameContains("javassist"))
                        .or(nameContains(".asm."))
                        .or(nameContains(".reflectasm."))
                        .or(nameStartsWith("sun.reflect"))
                        .or(allSkyWalkingAgentExcludeToolkit())
                        .or(ElementMatchers.<TypeDescription>isSynthetic()));
    
            JDK9ModuleExporter.EdgeClasses edgeClasses = new JDK9ModuleExporter.EdgeClasses();
            try {
                // 植入ByteBuddy运行时代码
                agentBuilder = BootstrapInstrumentBoost.inject(pluginFinder, instrumentation, agentBuilder, edgeClasses);
            } catch (Exception e) {
                logger.error(e, "SkyWalking agent inject bootstrap instrumentation failure. Shutting down.");
                return;
            }
    
            try {
                agentBuilder = JDK9ModuleExporter.openReadEdge(instrumentation, agentBuilder, edgeClasses);
            } catch (Exception e) {
                logger.error(e, "SkyWalking agent open read edge in JDK 9+ failure. Shutting down.");
                return;
            }
    
            agentBuilder
                .type(pluginFinder.buildMatch())
                .transform(new Transformer(pluginFinder))
                .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
                .with(new Listener())
                .installOn(instrumentation);
    
            try {
                // 启动相关服务,即所有实现BootService的接口
                ServiceManager.INSTANCE.boot();
            } catch (Exception e) {
                logger.error(e, "Skywalking agent boot failure.");
            }
    
            // 配置shutdownhook
            Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
                @Override public void run() {
                    ServiceManager.INSTANCE.shutdown();
                }
            }, "skywalking service shutdown thread"));
        }
    

    plugin扫描

    plugin扫描类似于spring boot的spring.factories扫描,当然springboot是通过spring.factories做SPI。而Skywalking使用JDK本身的SPI方式。具体扫描过程如下:

    pluginFinder = new PluginFinder(new PluginBootstrap().loadPlugins());
    
    public List<AbstractClassEnhancePluginDefine> loadPlugins() throws AgentPackageNotFoundException {
        AgentClassLoader.initDefaultLoader();
    
        PluginResourcesResolver resolver = new PluginResourcesResolver();
        List<URL> resources = resolver.getResources();
        ...
        return plugins;
    }
    public List<URL> getResources() {
        List<URL> cfgUrlPaths = new ArrayList<URL>();
        Enumeration<URL> urls;
        try {
            urls = AgentClassLoader.getDefault().getResources("skywalking-plugin.def");
    
            while (urls.hasMoreElements()) {
                URL pluginUrl = urls.nextElement();
                cfgUrlPaths.add(pluginUrl);
                logger.info("find skywalking plugin define in {}", pluginUrl);
            }
    
            return cfgUrlPaths;
        } catch (IOException e) {
            logger.error("read resources failure.", e);
        }
        return null;
    }
    

    由此可见skywalking是通过扫描skywalking-plugin.def来进行插件的装载。

    SpringCloud plugin

    这里以springcloud中NetflixFeignInstrumentation为例。

    首先skywalking-plugin.def中定义插件入口

    spring-cloud-feign-1.x=org.apache.skywalking.apm.plugin.spring.cloud.netflix.feign.v11.define.NetflixFeignInstrumentation
    
    public class NetflixFeignInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
        /**
         * Enhance class.
         */
        private static final String ENHANCE_CLASS = "org.springframework.cloud.netflix.feign.ribbon.LoadBalancerFeignClient";
    
        /**
         * Intercept class.
         */
        private static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.feign.http.v9.DefaultHttpClientInterceptor";
    
        @Override protected ClassMatch enhanceClass() {
            return byName(ENHANCE_CLASS);
        }
    
        @Override public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
            return new ConstructorInterceptPoint[0];
        }
    
        @Override public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
            return new InstanceMethodsInterceptPoint[] {
                new InstanceMethodsInterceptPoint() {
                    @Override public ElementMatcher<MethodDescription> getMethodsMatcher() {
                        return named("execute");
                    }
    
                    @Override public String getMethodsInterceptor() {
                        return INTERCEPT_CLASS;
                    }
    
                    @Override public boolean isOverrideArgs() {
                        return false;
                    }
                }
            };
        }
    }
    

    通过对org.springframework.cloud.netflix.feign.ribbon.LoadBalancerFeignClient的类进行构造器和方法的拦截,植入的拦截类为org.apache.skywalking.apm.plugin.feign.http.v9.DefaultHttpClientInterceptor。
    这里主要对LoadBalancerFeignClient的execute方法进行字节码重写。

    public class DefaultHttpClientInterceptor implements InstanceMethodsAroundInterceptor
    

    植入的拦截器实现了InstanceMethodsAroundInterceptor接口,其中需要重写三个方法:

    • beforeMethod
    • afterMethod
    • handleMethodException

    我们通过org.apache.skywalking.apm.plugin.feign.http.v9.DefaultHttpClientInterceptor的beforeMethod来看看做了什么操作:

    public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments,
                                 Class<?>[] argumentsTypes, MethodInterceptResult result) throws Throwable {
            Request request = (Request) allArguments[0];
            URL url = new URL(request.url());
            ContextCarrier contextCarrier = new ContextCarrier();
            int port = url.getPort() == -1 ? 80 : url.getPort();
            String remotePeer = url.getHost() + ":" + port;
            String operationName = url.getPath();
            FeignResolvedURL feignResolvedURL = PathVarInterceptor.URL_CONTEXT.get();
            if (feignResolvedURL != null) {
                try {
                    operationName = operationName.replace(feignResolvedURL.getUrl(), feignResolvedURL.getOriginUrl());
                } finally {
                    PathVarInterceptor.URL_CONTEXT.remove();
                }
            }
            if (operationName.length() == 0) {
                operationName = "/";
            }
            AbstractSpan span = ContextManager.createExitSpan(operationName, contextCarrier, remotePeer);
            span.setComponent(ComponentsDefine.FEIGN);
            Tags.HTTP.METHOD.set(span, request.method());
            Tags.URL.set(span, request.url());
            SpanLayer.asHttp(span);
    
            Field headersField = Request.class.getDeclaredField("headers");
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(headersField, headersField.getModifiers() & ~Modifier.FINAL);
    
            headersField.setAccessible(true);
            Map<String, Collection<String>> headers = new LinkedHashMap<String, Collection<String>>();
            CarrierItem next = contextCarrier.items();
            while (next.hasNext()) {
                next = next.next();
                List<String> contextCollection = new LinkedList<String>();
                contextCollection.add(next.getHeadValue());
                headers.put(next.getHeadKey(), contextCollection);
            }
            headers.putAll(request.headers());
    
            headersField.set(request, Collections.unmodifiableMap(headers));
        }
    

    这里解释了http调用时trace的信息是如何传输的:通过header进行传递,只不过这里不是通过拦截器的方式,而是在LoadBalancerFeignClient调用execute的方法时,通过ByteBuddy字节码重写来达到将trace信息存入header的目的。

    这里额外扩展一下header传入trace信息后,skywalking如何插装springmvc接收?有兴趣读者可以查看org.apache.skywalking.apm.plugin.spring.mvc.v5.define.RestControllerInstrumentation这个类,本质是对Springmvc的注解比如@GetMapping、@PostMapping等等进行插装来接收feign中header的信息。当然,skywalking对不同版本的spring实现不同。

    总结

    总的来说,skywalking这种方式使用起来方便,但伴随的是开发难度较大,需要对不同组件的底层都由了解才能灵活使用或者封装自己的插件。

    相关文章

      网友评论

          本文标题:skywalking简单原理

          本文链接:https://www.haomeiwen.com/subject/kduhhltx.html