监控之traceid

简介: 监控之前只总结了一篇《微服务-监控》,比较宏观。其中很多细节没有过深关注到,主要还是没有实践过,更没有去深度思考,所以很多有意思的技术点都错过了,比如traceid的生成,传递大牛圈总的大作《微服务系统架构之分布式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的知识结构丰满了很多


目录
相关文章
|
JSON 开发工具 git
工作五年多,idea插件推荐(一)
工作五年多,idea插件推荐(一)
|
机器学习/深度学习 弹性计算 TensorFlow
阿里云GPU加速:大模型训练与推理的全流程指南
随着深度学习和大规模模型的普及,GPU成为训练和推理的关键加速器。本文将详细介绍如何利用阿里云GPU产品完成大模型的训练与推理。我们将使用Elastic GPU、阿里云深度学习镜像、ECS(云服务器)等阿里云产品,通过代码示例和详细说明,带你一步步完成整个流程。
4029 0
|
SQL Arthas 缓存
使用篇丨链路追踪(Tracing)其实很简单:请求轨迹回溯与多维链路筛选
本章我们将以业务 Owner(小帅)的视角,逐步了解分布式链路追踪的各种基础用法:小到单次用户请求的异常根因诊断,大到全局系统的强弱依赖梳理,分布式链路追踪都能给予确定性答案。
20786 108
使用篇丨链路追踪(Tracing)其实很简单:请求轨迹回溯与多维链路筛选
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
1433 0
|
存储 缓存 编解码
|
人工智能 Serverless API
云原生应用开发平台CAP评测
CAP(Cloud Application Platform)是阿里云提供的一站式应用开发及管理平台,集成了Serverless计算、AI应用模板、先进开发者工具和企业级应用管理功能。本文结合官方文档与实际操作,全面评测了CAP的产品优势、实际操作体验及性能表现,展示了其在降低成本、提高开发效率和灵活性方面的卓越能力。
|
消息中间件 监控 Ubuntu
大数据-54 Kafka 安装配置 环境变量配置 启动服务 Ubuntu配置 ZooKeeper
大数据-54 Kafka 安装配置 环境变量配置 启动服务 Ubuntu配置 ZooKeeper
560 3
大数据-54 Kafka 安装配置 环境变量配置 启动服务 Ubuntu配置 ZooKeeper
|
SQL Java 数据库连接
|
缓存 NoSQL Java
【Azure Redis 缓存 Azure Cache For Redis】当使用Jedis客户端连接Redis时候,遇见JedisConnectionException: Could not get a resource from the pool / Redis connection los
【Azure Redis 缓存 Azure Cache For Redis】当使用Jedis客户端连接Redis时候,遇见JedisConnectionException: Could not get a resource from the pool / Redis connection los
1126 0
|
消息中间件 RocketMQ Docker
分布式事物【RocketMQ事务消息、Docker安装 RocketMQ、实现订单微服务、订单微服务业务层实现】(八)-全面详解(学习总结---从入门到深化)(上)
分布式事物【RocketMQ事务消息、Docker安装 RocketMQ、实现订单微服务、订单微服务业务层实现】(八)-全面详解(学习总结---从入门到深化)
656 0