前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >监控之traceid

监控之traceid

作者头像
码农戏码
发布2021-03-23 11:17:55
1.5K0
发布2021-03-23 11:17:55
举报
文章被收录于专栏:DDDDDD

监控之前只总结了一篇《微服务-监控》,比较宏观。其中很多细节没有过深关注到,主要还是没有实践过,更没有去深度思考,所以很多有意思的技术点都错过了,比如traceid的生成,传递

大牛圈总的大作《微服务系统架构之分布式traceId追踪参考实现》已经给出解决方案,但还是再主动总结一下

意义

为什么需要traceid,为了查看完整的调用链,一旦调用过程中出现问题,可以第一时间定位到问题现场

整个调用链是一棵树形结构,traceid的传递涉及到主干与支干,进程内与进程外

生成

原则是唯一不重复,比如现成的UUID

但UUID一是丑、无意义,二是string;

从字面意义以及未来落盘都不能说是最佳方案,比如想让traceid包含信息更丰富一些,能一眼看出此traceid是主干还是分支

此traceid有没有最终落盘(这儿涉及到落盘抽样率,每天服务处理海量请求,总不能每个traceid都落盘)

Random

这儿引申到如何更好地获取一个随机数又是一个课题,另开篇吧

传递

《熔断机制》中提过,服务调用是一个1->N扇出,调用链展现出对应的树形结构,但调用嵌套都不会深,一般两层就差不多了

  • traceId1
  • traceId1.1
    • traceId1.1.1
  • traceId2.1
  • traceId3.1

进程外

服务之间的传递

serverA --> serverB -- serverC

这儿在设计传输协议时,在协议头里面带上traceid

进程内

主干

这种场景ThreadLocal是最佳手法

支干

比如serviceA -- > remote.serviceB

trace是个树形结构,可以将remote.serviceB的traceId.parentId = serviceA.traceId

异步子任务

子线程可以通过InheritableThreadLocal传递traceid

顺带一下,InheritableThreadLocal的详细实现,先可补习一下ThreadLocal《解析ThreadLocal》

在创建Thread时,会从父线程的inheritableThreadLocals复制到子线程中去,这样在子线程中就能拿到在父线程中的赋值

代码语言:javascript
复制
/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

/*
 * InheritableThreadLocal values pertaining to this thread. This map is
 * maintained by the InheritableThreadLocal class.
 */
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
代码语言:javascript
复制
if (parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
线程池

如果没有线程池,以上就算是解决所有问题了,可实现毕竟是实现

代码语言:javascript
复制
/**
 * 子线程从父线程中取值
 * @throws InterruptedException
 */
private static void testThreadpool() throws InterruptedException {
    ExecutorService executorService = Executors.newFixedThreadPool(1);
    final ThreadLocal<String>  threadLocal = threadLocal();//new InheritableThreadLocal<>()
    threadLocal.set("parent");
    for(int i=0;i<1;i++) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() +" get parent value:" + threadLocal.get());
                threadLocal.set("sun");
                System.out.println(Thread.currentThread().getId() + "==" + threadLocal.get());
            }
        };
        executorService.execute(runnable);
        Thread.sleep(100);
        executorService.execute(runnable);
        Thread.sleep(100);
        System.out.println("main:" + threadLocal.get());
    }
    executorService.shutdown();
}

为了好重现问题,线程池大小为1,但会连续跑两次任务

代码语言:javascript
复制
pool-1-thread-1 get parent value:parent
11==sun
pool-1-thread-1 get parent value:sun
11==sun
main:parent

在第二次取父线程值时,却是第一次任务线程中的赋值,在线程池中子线程不能正常获取父线程值

线程池中,线程会复用,线程中的inheritableThreadLocals没有被清空

解决方法一是:池中线程数大于任务线程,让线程没有重用机会

代码语言:javascript
复制
ExecutorService executor = Executors.newFixedThreadPool(>=[任务线程数])

但在多线程应用中,明显不能解决问题,任务数肯定远远超过线程数

解决方法二是:自定义实现在使用完线程主动清空inheritableThreadLocals

阿里开源transmittable-thread-local就实现这样的功能

整体思路也是从主线程复制,使用,再清理

TtlRunnable 构造方法中,调用了 TransmittableThreadLocal.Transmitter.capture() 获取当前线程中所有的上下文,并储存在 AtomicReference 中 当线程执行时,调用 TtlRunnable run 方法,TtlRunnable 会从 AtomicReference 中获取出调用线程中所有的上下文,并把上下文给 TransmittableThreadLocal.Transmitter.replay 方法把上下文复制到当前线程。并把上下文备份 当线程执行完,调用 TransmittableThreadLocal.Transmitter.restore 并把备份的上下文传入,恢复备份的上下文,把后面新增的上下文删除,并重新把上下文复制到当前线程

transmittable-thread-local代码不多,但有很多亮点,可以自行膜拜

在此场景,transmittable-thread-local还是太重了,其实可以简单借鉴一下transmittable-thread-local的思路,自定义Runnable

代码语言:javascript
复制
public TransRunnable(Runnable runnable){
    this.runnable = runnable;
    //在创建时,获取父traceId
    this.parentId = TranceContext.getParentTrace();
}
@Override
public void run() {
    //
    String old = TranceContext.getParentTrace();
    //设置父traceid
    TranceContext.setParentTrace(parentId);
    runnable.run();
    //还原
    TranceContext.setParentTrace(old);
}

在创建子线程时,把父traceId带进去,就能在子线程业务方法中拿到父traceId,这样整个调用链也不会断

schedule

traceid生成,有主动请求时,会生成,但如果是个系统的定时任务呢?

  1. 让taskService调用一下入口,类似模拟用户行为
  2. 主动生成一个parent traceId

总结

到此,对于traceid的知识结构丰满了很多

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-05-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农戏码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 意义
  • 生成
    • Random
    • 传递
      • 进程外
        • 进程内
          • 主干
          • 支干
          • 异步子任务
          • 线程池
        • schedule
        • 总结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档