Skip to content

Latest commit

 

History

History
220 lines (153 loc) · 11.1 KB

developer-guide.md

File metadata and controls

220 lines (153 loc) · 11.1 KB

🎓 Developer Guide



📌 框架/中间件集成TTL传递

框架/中间件集成TTL传递,通过TransmittableThreadLocal.Transmitter 抓取当前线程的所有TTL值并在其他线程进行回放;在回放线程执行完业务操作后,恢复为回放线程原来的TTL值。

TransmittableThreadLocal.Transmitter提供了所有TTL值的抓取、回放和恢复方法(即CRR操作):

  1. capture方法:抓取线程(线程A)的所有TTL值。
  2. replay方法:在另一个线程(线程B)中,回放在capture方法中抓取的TTL值,并返回 回放前TTL值的备份
  3. restore方法:恢复线程B执行replay方法之前的TTL值(即备份)

示例代码:

// ===========================================================================
// 线程 A
// ===========================================================================

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

// (1) 抓取当前线程的所有TTL值
final Object captured = TransmittableThreadLocal.Transmitter.capture();

// ===========================================================================
// 线程 B(异步线程)
// ===========================================================================

// (2) 在线程 B中回放在capture方法中抓取的TTL值,并返回 回放前TTL值的备份
final Object backup = TransmittableThreadLocal.Transmitter.replay(captured);
try {
    // 你的业务逻辑,这里你可以获取到外面设置的TTL值
    String value = context.get();

    System.out.println("Hello: " + value);
    ...
    String result = "World: " + value;
} finally {
    // (3) 恢复线程 B执行replay方法之前的TTL值(即备份)
    TransmittableThreadLocal.Transmitter.restore(backup);
}

更多TTL传递的代码实现示例,参见 TtlRunnable.javaTtlCallable.java

当然可以使用TransmittableThreadLocal.Transmitter的工具方法runSupplierWithCapturedrunCallableWithCaptured和可爱的Java 8 Lambda语法 来简化replayrestore操作,示例代码:

// ===========================================================================
// 线程 A
// ===========================================================================

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

// (1) 抓取当前线程的所有TTL值
final Object captured = TransmittableThreadLocal.Transmitter.capture();

// ===========================================================================
// 线程 B(异步线程)
// ===========================================================================

String result = runSupplierWithCaptured(captured, () -> {
    // 你的业务逻辑,这里你可以获取到外面设置的TTL值
    String value = context.get();
    System.out.println("Hello: " + value);
    ...
    return "World: " + value;
}); // (2) + (3)

📟 关于Java Agent

Java Agent方式对应用代码无侵入

User Guide - 2.3 使用Java Agent来修饰JDK线程池实现类 说到了,相对修饰Runnable或是线程池的方式,Java Agent方式是对应用代码无侵入的。下面做一些展开说明。

构架图

按框架图,把前面示例代码操作可以分成下面几部分:

  1. 读取信息设置到TTL
    这部分在容器中完成,无需应用参与。
  2. 提交Runnable到线程池。要有修饰操作Runnable(无论是直接修饰Runnable还是修饰线程池)。
    这部分操作一定是在用户应用中触发。
  3. 读取TTL,做业务检查。
    SDK中完成,无需应用参与。

只有第2部分的操作和应用代码相关。

如果不通过Java Agent修饰线程池,则修饰操作需要应用代码来完成。

使用Java Agent方式,应用无需修改代码,即做到 相对应用代码 透明地完成跨线程池的上下文传递。

更多关于应用场景的了解说明参见文档需求场景

已有Java Agent中嵌入TTL Agent

这样可以减少Java启动命令行上的Agent的配置。

在自己的Agent中加上TTL Agent的逻辑,示例代码如下(YourXxxAgent.java):

import com.alibaba.ttl.threadpool.agent.TtlAgent;
import com.alibaba.ttl.threadpool.agent.TtlTransformer;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.util.logging.Logger;

public final class YourXxxAgent {
    private static final Logger logger = Logger.getLogger(YourXxxAgent.class.getName());

    public static void premain(String agentArgs, Instrumentation inst) {
        TtlAgent.premain(agentArgs, inst); // add TTL Transformer

        // add your Transformer
        ...
    }
}

关于Java AgentClassFileTransformer的如何实现可以参考:TtlAgent.javaTtlTransformer.java

注意,在bootclasspath上,还是要加上TTL Jar

-Xbootclasspath/a:/path/to/transmittable-thread-local-2.x.y.jar:/path/to/your/agent/jar/files

👢 Bootstrap ClassPath上添加通用库Jar的问题及其解决方法

TTL Agent的使用方式,需要将TTL Jar加到Bootstrap ClassPath上(通过Java命令行参数-Xbootclasspath);这样TTL的类与JDK的标准库的类(如java.lang.String)的ClassLoader是一样的,都在Bootstrap ClassPath上。

Bootstrap ClassPath上的类会优先于应用ClassPathJar被加载,并且加载ClassLoader不能被改。
# 当然技术上严格地说,通过Bootstrap ClassPath上的类(如标准库的类)是可以改ClassLoader的,但这样做一般只会带来各种麻烦的问题。关于ClassLoader及其使用注意的介绍说明 可以参见ClassLoader委托关系的完备配置

TTL Agent自己内部实现使用了Javassist,即在Bootstrap ClassPath上也需要添加Javassist。如果应用中也使用了Javassist,由于运行时会优先使用TTL Agent配置Bootstrap ClassPath上的Javassist,应用逻辑运行时实际不能选择/指定应用自己的Javassist的版本,带来了 应用需要的JavassistTTL Agent用的Javassist之间的兼容性风险。

可以通过 repackage依赖(即 重命名/改写 依赖类的包名)来解决这个问题。Maven提供了Shade插件,可以完成下面的操作:

  • repackage Javassist的类文件
  • 添加repackage过的JavassistTTL Jar

这样操作后,TTL Agent不需要依赖外部的Javassist依赖,效果上这样的shade过的TTL Jar是自包含的、在使用上是编译/运行时0依赖的,自然也规避了依赖冲突的问题。

🔨 如何编译构建

编译构建的环境要求: JDK 8+;用Maven常规的方式执行编译构建即可:
# 在工程中已经包含了符合版本要求的Maven,直接运行 工程根目录下的mvnw;并不需要先手动自己安装好Maven

# 运行测试Case
./mvnw test
# 编译打包
./mvnw package
# 运行测试Case、编译打包、安装TTL库到Maven本地
./mvnw install

#####################################################
# 如果使用你自己安装的 maven,版本要求:maven 3.3.9+
mvn install

发布操作列表

详见独立文档 发布操作列表

📚 相关资料

JDK core classes

Java Agent

Javassist

Maven Shade插件