Skip to content

Latest commit

 

History

History
120 lines (73 loc) · 8.34 KB

requirement-scenario.md

File metadata and controls

120 lines (73 loc) · 8.34 KB

🎨 需求场景

ThreadLocal的需求场景即是TTL的潜在需求场景,如果你的业务需要『在使用线程池等会池化复用线程的组件情况下传递ThreadLocal』则是TTL目标场景。

下面是几个典型场景例子。



🔎 1. 分布式跟踪系统 或 全链路压测(即链路打标)

关于『分布式跟踪系统』可以了解一下GoogleDapper(介绍的论文:中文| 英文)。分布式跟踪系统作为基础设施,不会限制『使用线程池等会池化复用线程的组件』,并期望对业务逻辑尽可能的透明。

分布式跟踪系统的实现的示意Demo参见DistributedTracerUseDemo.kt

从技术能力上讲,全链路压测 与 分布式跟踪系统 是一样的,即链路打标。

PS: 多谢 @wyzssw 对分布式追踪系统场景说明交流和实现上讨论建议:

🌵 2. 日志收集记录系统上下文

由于不限制用户应用使用线程池,系统的上下文需要能跨线程的传递,且不影响应用代码。

Log4j2 MDCTTL集成

Log4j2通过Thread Context提供了Mapped Diagnostic ContextMDC,诊断上下文)的功能,通过ThreadLocal/InheritableThreadLocal实现上下文传递。

Thread Context文档中提到了在使用线程池等会池化复用线程的组件(如Executors)时有问题,需要提供一个机制方案:

The Stack and the Map are managed per thread and are based on ThreadLocal by default. The Map can be configured to use an InheritableThreadLocal by setting system property isThreadContextMapInheritable to "true". When configured this way, the contents of the Map will be passed to child threads. However, as discussed in the Executors class and in other cases where thread pooling is utilized, the ThreadContext may not always be automatically passed to worker threads. In those cases the pooling mechanism should provide a means for doing so. The getContext() and cloneStack() methods can be used to obtain copies of the Map and Stack respectively.

即是TTL要解决的问题,提供Log4j2 MDCTTL集成,详见工程log4j2-ttl-thread-context-map。对应依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>log4j2-ttl-thread-context-map</artifactId>
    <version>1.3.0</version>
</dependency>

可以在 maven.org 查看可用的版本。

PS: 多谢 @bwzhang2011 和 @wuwen5 对日志场景说明交流和实现上讨论建议:

Logback MDCTTL集成

Logback的集成参见@ofpay提供的logback-mdc-ttl。对应依赖:

<dependency>
    <groupId>com.ofpay</groupId>
    <artifactId>logback-mdc-ttl</artifactId>
    <version>1.0.2</version>
</dependency>

可以在 maven.org 查看可用的版本。

这个集成已经在 线上产品环境 使用的。说明详见欧飞网的使用场景

👜 3. SessionCache

对于计算逻辑复杂业务流程,基础数据读取服务(这样的读取服务往往是个外部远程服务)可能需要多次调用,期望能缓存起来,以避免多次重复执行高成本操作。

同时,在入口发起不同的请求,处理的是不同用户的数据,所以不同发起请求之间不需要共享数据,这样也能避免请求对应的不同用户之间可能的数据污染。

因为涉及多个上下游线程,其实是Session级缓存。

通过Session级缓存可以

  • 避免重复执行高成本操作,提升性能。
  • 避免不同Session之间的数据污染。

更多讨论与使用方式参见@olove 提的Issue:讨论:Session级Cache场景下,TransmittableThreadLocal的使用

🛁 4. 应用容器或上层框架跨应用代码给下层SDK传递信息

举个具体的业务场景,在App EnginePAAS)上会运行由应用提供商提供的应用(SAAS模式)。多个SAAS用户购买并使用这个应用(即SAAS应用)。SAAS应用往往是一个实例为多个SAAS用户提供服务。
# 另一种模式是:SAAS用户使用完全独立一个SAAS应用,包含独立应用实例及其后的数据源(如DB、缓存,etc)。

需要避免的SAAS应用拿到多个SAAS用户的数据。一个解决方法是处理过程关联好一个SAAS用户的上下文,在上下文中应用只能处理(读/写)这个SAAS用户的数据。请求由SAAS用户发起(如从Web请求进入App Engine),App Engine可以知道是从哪个SAAS用户,在Web请求时在上下文中设置好SAAS用户ID。应用处理数据(DBWeb、消息 etc.)是通过App Engine提供的服务SDK来完成。当应用处理数据时,SDK检查数据所属的SAAS用户是否和上下文中的SAAS用户ID一致,如果不一致则拒绝数据的读写。

应用代码会使用线程池,并且这样的使用是正常的业务需求。SAAS用户ID的从要App Engine传递到下层SDK,要支持这样的用法。

上面场景使用TTL的整体构架

构架图

构架涉及3个角色:容器、用户应用、SDK

整体流程:

  1. 请求进入PAAS容器,提取上下文信息并设置好上下文。
  2. 进入用户应用处理业务,业务调用SDK(如DB、消息、etc)。
    用户应用会使用线程池,所以调用SDK的线程可能不是请求的线程。
  3. 进入SDK处理。
    提取上下文的信息,决定是否符合拒绝处理。

整个过程中,上下文的传递 对于 用户应用代码 期望是透明的。