写在前面

源码
本文看下通过如何实现单机版的链路追踪,了解了单机版的链路追踪,对于分布式场景的链路最终其实也就会了一大半,分布式场景下的链路追踪其实就是多了一个微服务调用另一个微服务时将traceId带上的工作。如下:
在这里插入图片描述
通过traceId就可以将整个调用链串起来了。本文先只来看下基于treadlocal的单机版链路追踪如何实现。

1:程序

定义premain:

package com.dahuyou.link.monitoring;

import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;
import java.lang.instrument.Instrumentation;

public class MyPreMain {

    public static void premain(String agentArgs, Instrumentation instrumentation) {
        System.out.println("MyPreMain.premain agentArgs is: " + agentArgs);

        AgentBuilder agentBuilder = new AgentBuilder.Default();

/*
        AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, javaModule) -> {
            builder = builder.visit(
                    Advice.to(MyByteBuddyAdvice.class)
                            .on(ElementMatchers.named("")));
*/
        /*AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, javaModule) -> {
            builder = builder.visit(
                    Advice.to(MyByteBuddyAdvice.class)
                            .on(ElementMatchers.isMethod()
                                    .and(ElementMatchers.any())
                                    .and(ElementMatchers.not(ElementMatchers.nameStartsWith("main")))));*/

            AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, javaModule) -> {
                builder = builder.visit(
                        Advice.to(MyByteBuddyAdvice.class)
                                .on(ElementMatchers.named("method1")
                                        .or(ElementMatchers.named("method2"))
                                        .or(ElementMatchers.named("method3"))));

                return builder;
        };

        agentBuilder = agentBuilder.type(ElementMatchers.nameStartsWith("com.dahuyou.link.monitoring")).transform(transformer).asDecorator();

        //监听
        AgentBuilder.Listener listener = new AgentBuilder.Listener() {
            @Override
            public void onDiscovery(String s, ClassLoader classLoader, JavaModule javaModule, boolean b) {

            }

            @Override
            public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule, boolean b, DynamicType dynamicType) {
//                System.out.println("onTransformation:" + typeDescription);
            }

            @Override
            public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule, boolean b) {

            }

            @Override
            public void onError(String s, ClassLoader classLoader, JavaModule javaModule, boolean b, Throwable throwable) {

            }

            @Override
            public void onComplete(String s, ClassLoader classLoader, JavaModule javaModule, boolean b) {

            }

        };
        // 将bytebuddy的插桩逻辑安装到instrument
        agentBuilder.with(listener).installOn(instrumentation);
    }
}

其中设置的切面类MyByteBuddyAdvice如下:

package com.dahuyou.link.monitoring;

import net.bytebuddy.asm.Advice;
import java.util.UUID;

public class MyByteBuddyAdvice {
    @Advice.OnMethodEnter()
    public static void enter(@Advice.Origin("#t") String className, @Advice.Origin("#m") String methodName) {
        String traceId = TrackManager.getCurrentTraceId();
        // a->b->c 当方法a执行时是没有traceId的,则创建并设置
        if (null == traceId) {
            traceId = UUID.randomUUID().toString();
            TrackContext.setTraceId(traceId);
        }
        // 记录该次方法调用对应的span,调用了几个方最终就生成几个span
        Span entrySpan = TrackManager.createEntrySpan(className, methodName);
        System.out.println("链路追踪:" + entrySpan.getTraceId() + ", 类: " + className + ", 方法:" + methodName + ", 线程:" + Thread.currentThread().getName());
    }

    /**
     * 方法栈帧从方法栈弹出时执行(方法执行结束)
     * @param className
     * @param methodName
     */
    @Advice.OnMethodExit()
    public static void exit(@Advice.Origin("#t") String className, @Advice.Origin("#m") String methodName) {
        System.out.println("--------------");
        TrackManager.getAllSpan();
    }

}

TrackContext用来用来存储traceId,类TrackManager用来维护方法调用对应的span信息,源码如下:

package com.dahuyou.link.monitoring;

public class TrackContext {

    private static final ThreadLocal<String> trackLocal = new ThreadLocal<String>();

    public static void clear() {
        trackLocal.remove();
    }

    public static String getTraceId() {
        String traceId = trackLocal.get();
        return traceId;
    }

    public static void setTraceId(String traceId) {
        /*
        放到treadlocal中,这样只要是在一个线程中通过get就能获取到值了
         */
        trackLocal.set(traceId);
    }
}
package com.dahuyou.link.monitoring;

import java.util.*;

public class TrackManager {

    private static final ThreadLocal<Stack<String>> track = new ThreadLocal<Stack<String>>();
    private static final ThreadLocal<Map<String, List<Span>>> trackWithSpan = new ThreadLocal<>();

    private static Span createSpan(String className, String methodName) {
        Map<String, List<Span>> stack = trackWithSpan.get();
        String traceId = TrackContext.getTraceId();

        if (stack == null) {
            stack = new HashMap<>();
            trackWithSpan.set(stack);
        }
        if (!stack.containsKey(traceId)) {
            List<Span> spanList = new ArrayList<>();
            stack.put(traceId, spanList);
        }
        // 创建方法调用span
        Span span = new Span();
        span.setClassName(className);
        span.setMethodName(methodName);
        span.setTraceId(traceId);
        // 添加span到traceid对应的span调用集合中
        stack.get(traceId).add(span);
        return span;
    }

    private static String createSpan() {
        Stack<String> stack = track.get();
        if (stack == null) {
            stack = new Stack<>();
            track.set(stack);
        }
        String linkId;
        if (stack.isEmpty()) {
            linkId = TrackContext.getTraceId();
            if (linkId == null) {
                linkId = "nvl";
                TrackContext.setTraceId(linkId);
            }
        } else {
            linkId = stack.peek();
            TrackContext.setTraceId(linkId);
        }
        return linkId;
    }

    public static String createEntrySpan() {
        String span = createSpan();
        Stack<String> stack = track.get();
        stack.push(span);
        return span;
    }

    public static Span createEntrySpan(String className, String methodName) {
        Span span = createSpan(className, methodName);
        return span;
    }


    public static String getExitSpan() {
        Stack<String> stack = track.get();
        if (stack == null || stack.isEmpty()) {
            TrackContext.clear();
            return null;
        }
        return stack.pop();
    }

    public static String getCurrentSpan() {
        Stack<String> stack = track.get();
        if (stack == null || stack.isEmpty()) {
            return null;
        }
        return stack.peek();
    }

    public static void getAllSpan() {
        System.out.println(trackWithSpan.get().get(TrackContext.getTraceId()));
    }

    public static String getCurrentTraceId() {
        return TrackContext.getTraceId();
    }
}

打包:
在这里插入图片描述
测试类:

package com.dahuyou.link.monitoring;

public class ApiTest {

    public static void main(String[] args) {

        //线程一
        new Thread(() -> new ApiTest().method1(), "线程思密达").start();
        new Thread(() -> new ApiTest().method1(), "线程萨瓦迪卡").start();

//        new ApiTest().method1();
    }


    public void method1() {
        System.out.println("测试结果:hi1");
        method2();
    }

    public void method2() {
        System.out.println("测试结果:hi2");
        method3();
    }

    public void method3() {
        System.out.println("测试结果:hi3");
    }

}

在测试类中我们启动了2个线程来模拟两次方法调用,后续我们就可以通过traceId来将方法调用串起来了。
配置javaagent:
在这里插入图片描述
运行:
在这里插入图片描述

写在后面

参考文章列表

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐