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

这个库带来怎样的好处和优势? #128

Closed
wilddylan opened this issue Feb 15, 2019 · 4 comments
Closed

这个库带来怎样的好处和优势? #128

wilddylan opened this issue Feb 15, 2019 · 4 comments

Comments

@wilddylan
Copy link

No description provided.

@oldratlee oldratlee added the ❓question Further information is requested label Feb 15, 2019
@oldratlee
Copy link
Member

oldratlee commented Feb 15, 2019

这个库能带来怎样的好处和优势

@wilddylan 好问题 ❤️ 文档中没正面说明,需要直接说明出来。 :)


回答一个方案『好处』和『优势』时,要说明的是『 解决一个问题(场景)另一其它方案对比』。
即 涉及3部分: 1) 解决的问题/场景,2) 其它解决方案, 3) 对比。

1. 关于 解决的问题/场景

简单地说:异步执行上下文的传递;
期望做到:透明/自动完成所有异步执行上下文的可定制、规范化的捕捉/传递。

更多可以看文档中的说明:功能需求场景

2. 其它解决方案

解决『异步执行上下文的传递』,朴素直接的实现方案 是 下面的示例代码。
注:示例中的异步执行 是 以 new Thread方式,实际代码的异步执行可能是ThreadPoolRxJava/ReactorKotlin协程 等等不同的方式。

import java.util.HashMap;
import java.util.Map;

public class Demo {
    public static void main(String[] args) {
        // 1. *业务逻辑*需要做的实现逻辑:获取当前上下文
        final Map<String, String> context = getContext();

        // 2. *业务逻辑*需要做的实现逻辑:捕捉上下文
        Map<String, String> contextCopy = new HashMap<>(context);
        Runnable asyncLogic = () -> {
            // 在异步执行逻辑中,
            // 3. 传递,通过 Lambda的外部变量 contextCopy
            // 4. 使用上下文 
            System.out.println(contextCopy);
        };
        new Thread(asyncLogic).start();
    }

    private static Map<String, String> getContext() {
        return contextHolder.get();
    }

    // 使用 ThreadLocal:
    //   - 可以 不同的线程(如执行不同的请求用户的处理) 有自己的Context。
    //   - 避免 Context 总是通过 函数参数 传递,中间路过的业务的逻辑 都关注/感知 框架的上下文
    private static final ThreadLocal<Map<String, String>> contextHolder = ThreadLocal.withInitial(HashMap::new);
}

这样做法的问题

从 业务使用 角度来看

  1. 繁琐
    业务逻辑自己 要知道 有哪些上下文、分别要如何获取,并一个一个去捕捉/传递。
    上面示例代码 只示意了 一个上下文。
  2. 依赖
    直接依赖 不同 上下文获取的逻辑/类(比如RPC的上下文、全链路跟踪的上下文、不同业务模块中的业务上下文,等等)。
  3. 静态(易漏)
    • 要事先知道 哪些上下文;如果有 新异步执行的上下文 出现了,业务逻辑要修改:添加新上下文传递的几行代码。
      即 因系统的上下文新增,业务的逻辑就跟进要修改。
    • 对于业务来说,不关心系统的上下文,即往往就可能遗漏,会是线上故障了。
    • 比如对阿里这样的系统,功能/组件多涉及的上下文多、逻辑流程长,
      上下文问题 实际上 是个 大的易错的架构问题。需要统一的解决方案。
  4. 定制性
    • 业务要关注『上下文的传递方式』。直接传引用?还是拷贝传值?拷贝是深拷贝还是浅拷贝?
      上面的示例代码实际上是拷贝了一层HashMap,即浅拷贝,实际业务中往往不会这么简单。
    • 『上下文的传递方式』这点 往往是 上下文的提供者(或说是 业务逻辑的框架部分)才能 决策处理好的,而 上下文的使用者(或说是 业务逻辑的应用部分)往往不(期望)知道上下文的传递方式。
    • 这也可以理解成是 依赖,即业务逻辑 依赖/关注/实现了 系统/架构的『上下文的传递方式』。

从 整体流程实现 角度来看

上下文传递流程的规范化

  • 上下文传递到了子线程 要做好 清理(或更准确地说是 要恢复成之前的上下文),需要业务逻辑去处理好。
    上面示例代码 实际上 没有解决 这方面的问题。
  • 如果 业务逻辑 对 清理 的 处理不正确:
    1. 如果 清理操作漏了:
      • 下一次执行可能是上次的,即『上下文的 污染/串号』,会导致业务逻辑错误。
      • 『上下文的 泄漏』,会导致内存泄漏问题。
    2. 如果 清理操作做多了,会出现上下文 丢失
  • 上面的问题,在业务开发中引发的Bug真是 屡见不鲜
    怎么设计一个『上下文传递流程』方案(即上下文的生命周期),保证 没有上面的问题?
  • 期望:上下文生命周期的操作 从业务逻辑中 分离出来;
    业务逻辑 不涉及 生命周期,就不会有 由清理引发这些问题了。
  • 整个流程/上下文生命周期 可以规范化成:捕捉、回放和恢复 3个操作,即CRR(capture/replay/restore)

3. 对比

好处和优势 对应的就是 上面的问题解决了,即做到:

透明/自动完成所有异步执行上下文的可定制、规范化的捕捉/传递。

基于示例代码说明如下:

import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;

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

public class Demo {
    public static void main(String[] args) {
        // # 从 业务使用 角度来看 #
        //
        // 1. 关于『繁琐』:
        //    无需关注上下文的传递(由库接管完成)。
        //    自然无需 业务逻辑自己 知道 有哪些上下文、分别要如何获取,并一个一个去捕捉/传递。
        // 2. 关于『依赖』:
        //    业务逻辑直接使用 TTL,不会依赖 不同 上下文获取的逻辑/类。
        // 3. 关于『静态(易漏)』
        //    由库接管完成 *所有* 上下文的传递,非静态 自动感知新加的上下文。
        // 4. 关于『定制性』:
        //    提供了`copy`方法,
        //    由框架本身(即上下文的提供者)定制好了『上下文的传递方式』,业务逻辑无需感知。
        //
        // # 从 整体流程实现 角度来看 #
        //
        // 1. 上下文传递流程的规范化
        //    上下文生命周期的操作 从业务逻辑中 分离出来,
        //    由库接管完成,业务逻辑无需感知。不会有生命周期相关 *屡见不鲜*的 Bug!
        Runnable asyncLogic = () -> {
            // 传递,在异步执行逻辑中,使用上下文
            System.out.println(contextHolder1.get());
            System.out.println(contextHolder2.get());
        };

        executorService.submit(asyncLogic);
    }

    private static final TransmittableThreadLocal<String> contextHolder1 = new TransmittableThreadLocal<String>();

    private static final TransmittableThreadLocal<Node> contextHolder2 = new TransmittableThreadLocal<Node>() {
        @Override
        protected Node copy(Node parentValue) {
            return new Node(parentValue.id); // 定制传递方式
        }

        @Override
        protected Node initialValue() {
            return new Node("1");
        }
    };

    private static class Node {
        final String id;

        public Node(String id) {
            this.id = id;
        }

        @Override
        public String toString() {
            return "Node{id='" + id + '\'' + '}'; 
        }
    }

    private static final ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newCachedThreadPool());
}

@wilddylan 你看看,我说的明白不? 不清楚,继续交流。

另外,推荐看一下 issue

@oldratlee oldratlee changed the title 所以 这个库 到底能带来怎样的好处和优势 这个库带来怎样的好处和优势? Feb 15, 2019
@oldratlee oldratlee self-assigned this Feb 19, 2019
@wilddylan
Copy link
Author

很棒唉 ~ 嘿嘿,为你点赞,快给我一个你加密的工号,我去给你点赞!

@oldratlee
Copy link
Member

oldratlee commented Mar 2, 2019

很棒唉 ~ 嘿嘿,为你点赞,快给我一个你加密的工号,我去给你点赞!

噗~

直接用我的id oldratlee 在内网就查到我了,
名字 在我的github主页上: https://github.com/oldratlee

PS: 这个Issue不用关了,好让大家可以方便地看到 ❤️ @wilddylan

@oldratlee
Copy link
Member

oldratlee commented May 30, 2022

Closed.
上面的讨论内容已并入项目文档主页: ✨ 使用TTL的好处与必要性

欢迎 大家在这个 issue 继续讨论 ❤️

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

2 participants