Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

分布式追踪系统场景下,如何使用TTL #53

Closed
wyzssw opened this issue Nov 29, 2015 · 33 comments
Closed

分布式追踪系统场景下,如何使用TTL #53

wyzssw opened this issue Nov 29, 2015 · 33 comments
Assignees
Milestone

Comments

@wyzssw
Copy link

wyzssw commented Nov 29, 2015

子线程无法修改父线程 threadlocal,不知道设计时是否支持该功能

ExecutorService service = Executors.newFixedThreadPool(1);

    ExecutorService executorService = TtlExecutors.getTtlExecutorService(service); 

     final TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>();
     parent.set("value-set-in-parent");

     executorService.submit(new Runnable() {

      @Override
      public void run() {
        System.out.println(parent.get());
        parent.set("asf");
      }
    });

     Thread.sleep(1000);
     String value = parent.get(); 
     System.out.println(value);

打印出

value-set-in-parent
value-set-in-parent
@oldratlee
Copy link
Member

oldratlee commented Nov 29, 2015

  • TransmittableThreadLocal 本身是 ThreadLocal(继承关系),不同的线程各自有一份,即是独立的。
  • TransmittableThreadLocalThreadLocal的加强,是能跨线程池继承Context。
  • ThreadLocal一样,往往是static,全局一份,通过TransmittableThreadLocal变量查看/修改的自己线程的那个拷贝。

所以你 测试到的现象 是 预期的功能。

示意代码调整如下,你看看,说明白了不? @wyzssw

ExecutorService service = Executors.newFixedThreadPool(1);
ExecutorService executorService = TtlExecutors.getTtlExecutorService(service); 

// 通过`TransmittableThreadLocal`变量查看/修改的自己线程的哪个拷贝
// 所以命名 不用Parent,往往按用途命名
// 且是 static 变量
static final TransmittableThreadLocal<String> storeTestContext = new TransmittableThreadLocal<String>();

// Use
public void testMethod() {
    storeTestContext.set("value-set-in-parent");

     executorService.submit(new Runnable() {
      @Override
      public void run() {
        // 继承自testMethod方法所在线程
        System.out.println("Before set in Runnable: " + testMethod.get()); 
        storeTestContext.set("asf");
        // 设置新值,不会影响testMethod线程
        System.out.println("After set in Runnable: " + testMethod.get()); 
      }
    });

     Thread.sleep(1000);
     String value = storeTestContext.get(); 
     System.out.println("In testMethod: " + value);
}

输出:

Before set in Runnable: value-set-in-parent
After set in Runnable: asf
In testMethod: value-set-in-parent

@oldratlee oldratlee added the ❓question Further information is requested label Nov 29, 2015
@oldratlee
Copy link
Member

oldratlee commented Nov 29, 2015

可以先说说你的需求/要解决的问题。再看看合适的解决方案。

如果 场景/需求 是合理的,可以考虑提供功能: 在其它线程中 去设置 本线程的Context。
# 个人感觉,在实际的场景下,应该是有其它更合理的做法。 😄

@wyzssw
Copy link
Author

wyzssw commented Nov 30, 2015

我现在做一套分布式追踪系统,比如一次RPC调用,client-A调用server-Bserver-B要多线程并行调用server-Cserver-D获取结果,server-B聚合结果返回给client-A

框架里面要对这次调用链记录traceId和spanId(类似淘宝鹰眼的rpcId),在上述例子中,client-Aserver-B的调用标识(spanId)为1,server-Bserver-C的调用标识(spanId)为1.1server-Bserver-D的调用标识(spanId)为1.2

在同一trace调用链中,每个spanId是唯一的,server-B将接收到由client-A生成的spanId放到TransmittableThreadLocal<String>变量名spanIdLocal,还有一个变量TransmittableThreadLocal<AtmicLong>作为计数器,变量名autoIncrLocal

子线程 生成spanId时,使用String spanId=spanIdLocal.get()+"."+autoIncrLocal.get().incrementAndGet()

父线程通过future.get()获取server-Bserver-C的结果后,将autoIncrLocal清零--autoIncrLocal.get().set(0)以供下次RPC调用链使用。

因为TransmittableThreadLocal 无法满足我的需求,我现在换成ConcurrentMap<Long, AtomicLong> autoIncr = new ConcurrentHashMap<>(100); keytraceIdvalue是计数器,来做的。

@oldratlee
Copy link
Member

oldratlee commented Nov 30, 2015

明白你的场景了,分布式追踪系统是个重要的基础系统 👍

把你说的Server调用,画个示意图,TraceId是同一个,括号里是SpanId。

Client A(ROOT)  +---> Server B(1) +---> Server C(1.1) +---> Server Y(1.1.1)
                |                 |                   |
                |                 |                   \---> Server Z(1.1.2)
                |                 \---> Server D(1.2)
                |
                \---> Server E(2) +---> Server F(2.1)
                                  |
                                  \---> Server G(2.2)

用全局的ConcurrentMap实现 TraceId对应的 下级Server的SpanId的使用记录,是简单直接的,无需TransmittableThreadLocal


上面图是按Server维度在说明,Server之间是RPC调用,TraceId/SpanId的传递可以通过Rpc调用的Context来完成。

对于上面Server C调用 Server Z,展开到In-JVM方法调用维度,示意图如下:

Server C(1.1)                             ------> Server Z(1.1.2)
MethodC1 =====> MethodC2 =*=*=> MethodC3  ------> MethodZ1
        直接调用          异步调用
                   如,MethodC2提交到Runnable到线程池,
                   在Runnable中调用MethodC3
  1. MethodB1 到 MethodB2 的TraceId传递
    # 这里可以用ThreadLocal,以不污染 业务方法 的参数列表。
  2. MethodB2 到 MethodB3 的 TraceId传递
    # 这个地方可以考虑用TTL

上面的场景你是如何解决的? @wyzssw
可能我的思路复杂化了 😄

PS: 本Server一级的SpanId,如上面 Server C1.1,和TraceId一样需要保持和传递。

@wyzssw
Copy link
Author

wyzssw commented Dec 1, 2015

多谢回复

是的,和你的思路是一样的,traceId 的多线程传递我用的就是TransmittableThreadLocal,唯一一点侵入就是需要业务代码中使用TtlExecutors修饰下线程池。

@oldratlee
Copy link
Member

oldratlee commented Dec 3, 2015

关于『用全局的ConcurrentMap实现 TraceId对应的 下级Server的SpanId的使用记录』,调用完成时,需要清除Map中的 TraceId 的条目,避免内存泄漏,TraceId随着时间推移会有很多。
# 相对直接调用,异步调用的什么时候执行完成是不确定的,所以『当前Server一级的SpanId的使用记录』的持有的释放要麻烦些。

你是如何解决的?

另外,关于『唯一一点侵入就是需要业务代码中使用TtlExecutors修饰下线程池』,如果你可以接管运行容器(如Tomcat)的话,可以配置JVM参数,这样可以对业务代码透明无需业务修饰线程池,详见用户文档 2.3 使用Java Agent来修饰JDK线程池实现类

@wyzssw
Copy link
Author

wyzssw commented Dec 4, 2015

你到提醒了我,我之前没有考虑oneway方法的rpc请求,只考虑了父线程会等到所有子线程的结果,每次都是server receive时put操作,server send时remove操作,我再想下好的方案,或者大牛你有好的方案还请不吝赐教。

jvm参数加agent,业务方估计会比较排斥,因为不知道agent修改了哪些类,会担心影响性能和安全。

@oldratlee
Copy link
Member

客气了 :)

关于『jvm参数加agent,业务方估计会比较排斥,因为不知道agent修改了哪些类,会担心影响性能和安全』:

  1. 关于『性能』,可以自己再测试一下,拿到结果来确定实际的情况。
  2. 关于『安全』,所有的代码是开源白盒的,确定实现机制和修改内容即可。

上面的事情是一次性的,比起业务不透明,让各个使用方在不同的应用代码中保证,如果可以做到,个人感觉投入是值得的。

当然,前提是『你可以接管容器的运行方式』: 😄

  • 容器部署不是开发以各不相同的方式完成的,比如PE
  • 或者 更直接点,只要运行的脚本是统一,即可,这样开发自己发布也OK,因为JVM参数通过运行脚本来设置的。

@wyzssw
Copy link
Author

wyzssw commented Dec 4, 2015

现在我们后端服务没有使用tomcat,只有api层使用了tomcat,后端各个服务都是一个单独的thrift-server,如果要添加agent需要事先将multithread.context-1.1.0-SNAPSHOT.jar部署到线上服务器上,或者放在工程lib目录里,对发布部署迁移升级带来些额外工作量。

@oldratlee
Copy link
Member

oldratlee commented Dec 4, 2015

你到提醒了我,我之前没有考虑oneway方法的rpc请求,只考虑了父线程会等到所有子线程的结果,每次都是server receive时put操作,server send时remove操作,我再想下好的方案,或者大牛你有好的方案还请不吝赐教。

我想到的解法是对『traceId』做引用计数。

具体参见 DistributedTracerUseDemo.kt

运行结果:

DEBUG: Increase reference counter(1) for traceId traceId-111 in thread main
DEBUG: Increase reference counter(2) for traceId traceId-111 in thread main
Do Rpc invocation to server server 2 with {traceId=traceId-111, spanId=baseSpanId-1.1.1}
Do Rpc invocation to server server 3 with {traceId=traceId-111, spanId=baseSpanId-1.1.3}
Do Rpc invocation to server server 1 with {traceId=traceId-111, spanId=baseSpanId-1.1.2}
DEBUG: Decrease reference counter(1) for traceId traceId-111 in thread Executors
DEBUG: Decrease reference counter(0) for traceId traceId-111 in thread main
DEBUG: Clear traceId2LeafSpanIdInfo for traceId traceId-111 in thread main

PS: 上代码省时间,不附文字说明了~ 😸

@oldratlee
Copy link
Member

oldratlee commented Dec 4, 2015

当然要实现简单的话,跨线程池时,让用户自己传递一下Context。示意实现如下:

final Object traceContext =  TraceContext.borrowTraceContext(); // 计数加1

executor.submit(new Runnable() {
    public void run() {
        TraceContext.returnAndSetTraceContext(traceContext); // 计数减1

        // biz code

        // 执行结束,没有清理ThreadLocal中的Context!
        // 但是一个线程就一个Context没清,线程数有限,Context占用内存一般很小,可以接受。
    }
});

这样 让用户使用麻烦一些,但完全避开TTL及相应的修饰操作。

但 下面的逻辑还是有的:

  • ThreadLocal。不跨线程时,用来传递TraceIdSpanId
  • Context引用计数

@wyzssw
Copy link
Author

wyzssw commented Dec 4, 2015

太赞了,借助的TransmittableThreadLocal submit的前置和后置方法 进行引用计数操作完美解决了这个问题。不过我使用的1.2.0版本没法成功执行DistributedTracerUseDemo.java,子线程runnable对象退出未执行下列方法

      @Override
        protected void afterExecute() {
            decreaseSpanIdRefCounter();
        }

我clone了distributed-tracer-support分支是运行成功了,可否将新版本发布到maven仓库中?

我在thrift-rpc框架中添加了tracerFilter可以在client端或者service端执行invokeBeforeinvokeBefore 方法,添加增减计数操作,但是如果父线程执行比较快,还未来得及执行子线程,父线程退出,计数操作为0就清空了计数器,使用TTLsubmit前执行计数加1,完美解决了这个问题。

我的trace框架只开放给使用方TraceIdContext.getTraceId()方法 ,让其可以将traceId打到他自定义的日志中,至于span等信息,我不希望用户关心,dapper论文中也强调分布式追踪要对应用透明

@oldratlee
Copy link
Member

不过我使用的1.2.0版本没法成功执行 DistributedTracerUseDemo.java,子线程runnable对象退出未执行方法afterExecute
我clone了distributed-tracer-support分支是运行成功了,可否将新版本发布到maven仓库中?

是的,distributed-tracer-support分支有修复这个问题的提交。
尽量今天会合到master,并发布。

trace框架只开放给使用方TraceIdContext.getTraceId()方法 ,让其可以将traceId打到他自定义的日志中,

『trace框架只开放给使用方TraceIdContext.getTraceId()方法』赞成!


PS 推荐建议:

  • TraceIdContext.getTraceId() 类名改成 TraceContext.getTraceId() ,概念范围上比较自然些,也方便把其它信息的相关方法放到 TraceContext类中。
  • 如果让用户帮助触发做引用计数,方法名推荐如borrow/return。这样可以体现要配套调用 且 不能多次调用(不冥等) 的契约。

至于span等信息,我不希望用户关心,dapper论文中也强调分布式追踪要对应用透明

👍
Dapper之前了解过,论文还一直没有去读

@wyzssw
Copy link
Author

wyzssw commented Dec 5, 2015

👍 多谢大牛的指点,我会在代码里改正,又学到不少东西,之前你写的show-busy-java-threads.sh我一直在用 😄

@oldratlee oldratlee added this to the 1.2.1 milestone Dec 6, 2015
@oldratlee oldratlee changed the title children thread can not modify parent thread-local 分布式追踪系统场景下,如何使用MTC Dec 6, 2015
@oldratlee
Copy link
Member

oldratlee commented Dec 6, 2015

Fix了 『没有 afterExecute 回调 #54

1.2.1 release 并 发到了Maven中央库了。

关于前面说的『引用计数』解法,在Java里因为有GC,复杂化了。

使用WeakReference来解更合适,实现上用了Guava强大的CacheBuilder,参见代码Cache<String, LeafSpanIdInfo> traceId2LeafSpanIdInfo

详见Demo DistributedTracerUseDemo_WeakReferenceInsteadOfRefCounter.java

运行结果如下:

......
Do Rpc invocation to server server 1 with {traceId=traceId_XXXYYY39, spanId=1.1.1}
Finished Rpc call traceId_XXXYYY39 with span LeafSpanIdInfo{current=3}.
Do Rpc invocation to server server 3 with {traceId=traceId_XXXYYY38, spanId=1.1.2}
Do Rpc invocation to server server 1 with {traceId=traceId_XXXYYY40, spanId=1.1.1}
Finished Rpc call traceId_XXXYYY40 with span LeafSpanIdInfo{current=3}.
Do Rpc invocation to server server 3 with {traceId=traceId_XXXYYY39, spanId=1.1.2}
Do Rpc invocation to server server 2 with {traceId=traceId_XXXYYY33, spanId=1.1.3}
Do Rpc invocation to server server 3 with {traceId=traceId_XXXYYY41, spanId=1.1.2}
Do Rpc invocation to server server 1 with {traceId=traceId_XXXYYY41, spanId=1.1.1}
Finished Rpc call traceId_XXXYYY41 with span LeafSpanIdInfo{current=3}.
Do Rpc invocation to server server 3 with {traceId=traceId_XXXYYY40, spanId=1.1.2}
Do Rpc invocation to server server 1 with {traceId=traceId_XXXYYY42, spanId=1.1.1}
Finished Rpc call traceId_XXXYYY42 with span LeafSpanIdInfo{current=3}.
Do Rpc invocation to server server 2 with {traceId=traceId_XXXYYY34, spanId=1.1.3}
Do Rpc invocation to server server 3 with {traceId=traceId_XXXYYY42, spanId=1.1.2}
Do Rpc invocation to server server 3 with {traceId=traceId_XXXYYY43, spanId=1.1.2}
Do Rpc invocation to server server 1 with {traceId=traceId_XXXYYY43, spanId=1.1.1}
Finished Rpc call traceId_XXXYYY43 with span LeafSpanIdInfo{current=3}.
DEBUG: Remove traceId traceId_XXXYYY19 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY12 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY5 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY2 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY13 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY40 in thread main by cause COLLECTED: null
Do Rpc invocation to server server 2 with {traceId=traceId_XXXYYY42, spanId=1.1.3}
DEBUG: Remove traceId traceId_XXXYYY27 in thread main by cause COLLECTED: null
Do Rpc invocation to server server 2 with {traceId=traceId_XXXYYY43, spanId=1.1.3}
DEBUG: Remove traceId traceId_XXXYYY10 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY21 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY32 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY29 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY35 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY8 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY36 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY18 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY26 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY39 in thread main by cause COLLECTED: null
Do Rpc invocation to server server 2 with {traceId=traceId_XXXYYY44, spanId=1.1.1}
Do Rpc invocation to server server 3 with {traceId=traceId_XXXYYY44, spanId=1.1.3}
Do Rpc invocation to server server 1 with {traceId=traceId_XXXYYY44, spanId=1.1.2}
Finished Rpc call traceId_XXXYYY44 with span LeafSpanIdInfo{current=4}.
Do Rpc invocation to server server 1 with {traceId=traceId_XXXYYY45, spanId=1.1.1}
Finished Rpc call traceId_XXXYYY45 with span LeafSpanIdInfo{current=4}.
Do Rpc invocation to server server 3 with {traceId=traceId_XXXYYY45, spanId=1.1.3}
......

# 老Demo在 DistributedTracerUseDemo.java

@oldratlee oldratlee self-assigned this Dec 6, 2015
@wyzssw
Copy link
Author

wyzssw commented Dec 6, 2015

多谢发布,之前考虑过用guava weakvalue,但是无法达到需求,我看新demo也有这个问题 ,父线程退出,子线程还没来得及执行,会出现强引用退出,只剩下cache中弱引用触发remove。
我在127行和135行分别打了断点会出现LeafSpanIdInfo is NOT present, Bug情况

new Thread(new Runnable() {
            @Override
            public void run() {
                syncMethod_ByNewThread(); //断点处
            }
        }, "Thread-by-new").start();

oldratlee added a commit that referenced this issue Dec 7, 2015
oldratlee added a commit that referenced this issue Dec 7, 2015
@oldratlee
Copy link
Member

oldratlee commented Dec 7, 2015

我看新demo也有这个问题 ,父线程退出,子线程还没来得及执行,会出现强引用退出,只剩下cache中弱引用触发remove。

问题Fix了,解决方法:让TransmittableThreadLocal 持有 LeafSpanIdInfo
TransmittableThreadLocal 的持有是安全的:线程退出时/AfterExecute后,会被释放,不影响GC

详见提交 199e7de

PS:
虽然『让TransmittableThreadLocal 持有LeafSpanIdInfo』这个做法理解起来有些细节,但比起引用计数,这个做法的实现简单得多了。

运行结果如下:

......
Do Rpc invocation to server server 3 with {traceId=traceId_XXXYYY38, spanId=1.1.3}
Do Rpc invocation to server server 2 with {traceId=traceId_XXXYYY39, spanId=1.1.1}
Do Rpc invocation to server server 3 with {traceId=traceId_XXXYYY39, spanId=1.1.3}
Do Rpc invocation to server server 1 with {traceId=traceId_XXXYYY39, spanId=1.1.2}
Finished Rpc call traceId_XXXYYY39 with span LeafSpanIdInfo{current=4}.
Do Rpc invocation to server server 2 with {traceId=traceId_XXXYYY40, spanId=1.1.1}
Do Rpc invocation to server server 3 with {traceId=traceId_XXXYYY40, spanId=1.1.3}
Do Rpc invocation to server server 1 with {traceId=traceId_XXXYYY40, spanId=1.1.2}
Finished Rpc call traceId_XXXYYY40 with span LeafSpanIdInfo{current=4}.
Do Rpc invocation to server server 2 with {traceId=traceId_XXXYYY41, spanId=1.1.1}
Do Rpc invocation to server server 3 with {traceId=traceId_XXXYYY41, spanId=1.1.3}
Do Rpc invocation to server server 1 with {traceId=traceId_XXXYYY41, spanId=1.1.2}
Finished Rpc call traceId_XXXYYY41 with span LeafSpanIdInfo{current=4}.
Do Rpc invocation to server server 2 with {traceId=traceId_XXXYYY42, spanId=1.1.1}
Do Rpc invocation to server server 1 with {traceId=traceId_XXXYYY42, spanId=1.1.2}
Finished Rpc call traceId_XXXYYY42 with span LeafSpanIdInfo{current=4}.
Do Rpc invocation to server server 3 with {traceId=traceId_XXXYYY42, spanId=1.1.3}
DEBUG: Remove traceId traceId_XXXYYY38 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY28 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY14 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY41 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY11 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY3 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY33 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY20 in thread main by cause COLLECTED: null
DEBUG: Remove traceId traceId_XXXYYY25 in thread main by cause COLLECTED: null
......

@oldratlee
Copy link
Member

oldratlee commented Dec 7, 2015

『让TransmittableThreadLocal持有LeafSpanIdInfo』的做法 是 正解!

再看了一下代码,这样做了之后,不再需要CacheBuilder,实现 非常的简单直接了!

实现的简单直接 反过来体现保障的是 设计的合理正确。

详见 提交 ed73383

PS:

虽然上面的修改之后,DEBUG: Remove traceId traceId_XXXYYY25 in thread main by cause COLLECTED: null 这个日志没了(通过CacheBuilder的回调输出DEBUG日志,说明GC了)。

上个版本有这个日志,说明『让TransmittableThreadLocal持有LeafSpanIdInfo』的做法,GC上是OK的。

实际测试中,可以通过性能测试 的 内存使用情况,来确定 没有内存泄漏问题。
# 为了让性能测试 更容易发现问题,在测试时,让TransmittableThreadLocalContext中多持有一个测试用时Dummy大对象即可。

PPS:

现在看来之前你认为,用TransmittableThreadLocal来记录LeafSpanIdInfo的做法,在直觉上 就已经是对的了!! 👍

@wyzssw
Copy link
Author

wyzssw commented Dec 7, 2015

经过你这么一说,我也明白了,TransmittableThreadLocal在传递给子线程时并不是深拷贝,用版本1.2.0也可以,只不过要改下类型

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;

import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;
import com.google.common.base.Optional;


public class Test {
  public static void main(String[] args) throws InterruptedException {
    ExecutorService service = Executors.newFixedThreadPool(10);
    ExecutorService executorService = TtlExecutors.getTtlExecutorService(service);

    final TransmittableThreadLocal<Optional<AtomicLong>> parent =
        new TransmittableThreadLocal<Optional<AtomicLong>>();
    parent.set(Optional.of(new AtomicLong(0)));

    System.out.println("Parent-start at spanId " + parent.get().get().incrementAndGet());

    // 挤满线程
    for (int i = 0; i < 10; i++) {
      service.submit(new Runnable() {
        @Override
        public void run() {}
      });
    }

    // 提交任务,执行rpc调用
    for (int i = 0; i < 30; i++) {
      executorService.submit(new Runnable() {

        @Override
        public void run() {
          Optional<AtomicLong> spanIncr = parent.get();
          if (spanIncr.isPresent()) {
            // 打印输出子线程所需的spanId
            System.out.println("Sub start at spanId " + spanIncr.get().incrementAndGet());
          }
        }
      });
    }
    // 打印父线程退出的spanId
    System.out.println("Parent-end at spanId " + parent.get().get().incrementAndGet());
  }
}

输出

Parent-start at spanId 1
Sub start at spanId 4
Sub start at spanId 7
Sub start at spanId 10
Sub start at spanId 12
Sub start at spanId 8
Sub start at spanId 9
Sub start at spanId 17
Sub start at spanId 6
Sub start at spanId 19
Sub start at spanId 5
Sub start at spanId 21
Sub start at spanId 3
Sub start at spanId 23
Sub start at spanId 2
Sub start at spanId 25
Sub start at spanId 24
Sub start at spanId 27
Sub start at spanId 22
Sub start at spanId 29
Sub start at spanId 20
Sub start at spanId 18
Parent-end at spanId 16
Sub start at spanId 15
Sub start at spanId 14
Sub start at spanId 13
Sub start at spanId 11
Sub start at spanId 32
Sub start at spanId 31
Sub start at spanId 30
Sub start at spanId 28
Sub start at spanId 26

『为了让性能测试 更容易发现问题,在测试时,让TransmittableThreadLocal的Context中多持有一个测试用时Dummy大对象即可』,:+1: 新技能get√,多谢指点 :smile:

@oldratlee
Copy link
Member

oldratlee commented Dec 7, 2015

缺省是浅拷贝,可以通过覆盖copy方法自定义。这个行为 1.2.01.2.1 是一样的。
1.2.1只修复了afterExecute回调有的bug(上面引用计数的方案依赖这个回调的正确性)。

PS:
可以不用Optional,代码看起来也更简洁,因为后面的异步操作的线程一定是继承了context的。你可以试试。

@wyzssw
Copy link
Author

wyzssw commented Dec 7, 2015

嘿嘿,果然,去除Optional也行:+1:

@oldratlee
Copy link
Member

oldratlee commented Dec 7, 2015

多谢反馈,发现了bug,也一起深入梳理了 分布式追踪场景下TransmittableThreadLocal的使用与设计方式。 👍

很愉快 😋

后面有什么问题欢迎交流~ 👏
先close了

@wyzssw
Copy link
Author

wyzssw commented Dec 8, 2015

嗯,和牛人交流,收获很多,以后多多交流:clap:

@oldratlee
Copy link
Member

oldratlee commented Dec 11, 2015

通过finalize方法,重新输出GC操作的Debug日志。 参见

可以确认到在Main线程结束前, 100% 回收了TransmittableThreadLocal持有的DtTransferInfo对象。

@zavakid
Copy link
Member

zavakid commented Jun 15, 2017

👍 cool

@duguyixiaono1
Copy link

学习

@oldratlee oldratlee changed the title 分布式追踪系统场景下,如何使用MTC 分布式追踪系统场景下,如何使用TTL Mar 13, 2018
@blurooo
Copy link

blurooo commented Mar 6, 2019

看得我跳了起来

@oldratlee oldratlee added the apm label Dec 12, 2019
@wysstartgo
Copy link

学习了

@huangbuhuan
Copy link

学习了

2 similar comments
@yuanrengu
Copy link

学习了

@simplifyOurLife
Copy link

学习了

@zzq666666
Copy link

学习了,膜拜一下。对技术追求极致,才能不断进步。

@linglinghu5
Copy link

学习了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

12 participants