监控之traceid

监控之前只总结了一篇《微服务-监控》,比较宏观。其中很多细节没有过深关注到,主要还是没有实践过,更没有去深度思考,所以很多有意思的技术点都错过了,比如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复制到子线程中去,这样在子线程中就能拿到在父线程中的赋值

/* 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;
if (parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

线程池

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

/**
 * 子线程从父线程中取值
 * @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,但会连续跑两次任务

pool-1-thread-1 get parent value:parent
11==sun
pool-1-thread-1 get parent value:sun
11==sun
main:parent

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

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

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

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

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的知识结构丰满了很多

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容

  • 前言 ThreadLocal解决了在多个线程针对一个变量维护不同值的功能,如果你想在同一个线程内传递一些值,那么就...
    土豆肉丝盖浇饭阅读 4,166评论 1 8
  • 转自CSDN文章:Java面试题集 求职是在每个技术人员的生涯中都要经历多次。对于我们大部分人而言,在进入自己心仪...
    流浪java阅读 802评论 0 12
  • 面向对象的三个特征 封装,继承,多态.这个应该是人人皆知.有时候也会加上抽象. 多态的好处 允许不同类对象对同一消...
    Blizzard_liu阅读 1,302评论 0 6
  • 儿子努力你就是最棒的 儿子现二年级,周一至周五大学校,周六学屋,周末钢琴画画练字。从周一至周日,一点空闲时间都没有...
    香小厨阅读 283评论 1 1
  • 这半个月的读书计划搁浅 洛丽塔看了100页,傅雷家书看了10页 群山回唱还没有看完,492页 红玫瑰与白玫瑰,50...
    简夕简西阅读 230评论 1 2