CAT中实现异步请求的调用链查看

简介: CAT(Central Application Tracking),是美团点评基于 Java 开发的一套开源的分布式实时监控系统。美团点评基础架构部希望在基础存储、高性能通信、大规模在线访问、服务治理、实时监控、容器化及集群智能调度等领域提供业界领先的、统一的解决方案,CAT 目前在美团点评的产品定位是应用层的统一监控组件,在中间件(RPC、数据库、缓存、MQ 等)框架中得到广泛应用,为各业务线提供系统的性能指标、健康状况、实时告警等服务。

CAT简介

CAT(Central Application Tracking),是美团点评基于 Java 开发的一套开源的分布式实时监控系统。美团点评基础架构部希望在基础存储、高性能通信、大规模在线访问、服务治理、实时监控、容器化及集群智能调度等领域提供业界领先的、统一的解决方案,CAT 目前在美团点评的产品定位是应用层的统一监控组件,在中间件(RPC、数据库、缓存、MQ 等)框架中得到广泛应用,为各业务线提供系统的性能指标、健康状况、实时告警等服务。

准备工作

对于同步请求API,CAT服务端自然是可以看到的。同步请求API的实例可以参考之前的文章《五分钟后,你将学会在SpringBoot项目中如何集成CAT调用链》。但对于异步请求API,因为不在同一线程中,在子线程中无法获取到父线程消息树,所以在CAT服务端是无法看到的对应请求。

首先,写一个类实现Cat.Context接口,用于存放消息树的上下文信息:

public class CatContext implements Cat.Context {

    private Map<String, String> properties = new HashMap<>();

    @Override
    public void addProperty(String key, String value) {
        properties.put(key, value);
    }

    @Override
    public String getProperty(String key) {
        return properties.get(key);
    }

    @Override
    public String toString() {
        return "CatContext{"
                + "properties=" + properties + '}';
    }
}

我们可以先父线程消息树的上下文信息保存下来,然后在子线程使用。先写一个存放上下文信息的地方:

public class ContextWarehouse {
    private static ThreadLocal<CatContext> contextThreadLocal = new ThreadLocal();

    public static void setContext(final CatContext context) {
        contextThreadLocal.set(context);
    }

    public static CatContext getContext() {
        //先从ContextWarehouse中获取上下文信息
        CatContext context = contextThreadLocal.get();
        if (context == null) {
            context = new CatContext();
            Cat.logRemoteCallClient(context);
        }
        return context;
    }
}

实现Callable接口,创建一个自定义的类,实现了在子线程中存放父线程的上下文信息的功能:

public class OneMoreCallable<V> implements Callable<V> {

    private CatContext catContext;

    private Callable<V> callable;

    public DdCallable(final Callable<V> callable) {
        this.callable = callable;
        this.catContext = new CatContext();
        //获取父线程消息树的上下文信息
        Cat.logRemoteCallClient(this.catContext);
    }
    
    @Override
    public V call() throws Exception {
        //保存父线程消息树的上下文信息到子线程
        ContextWarehouse.setContext(this.catContext);
        return callable.call();
    }
}

定义一些常量,在调用API时作为header中的key:

public class CatHttpConstants {
    public static final String CAT_HTTP_HEADER_CHILD_MESSAGE_ID = "DD-CAT-CHILD-MESSAGE-ID";
    public static final String CAT_HTTP_HEADER_PARENT_MESSAGE_ID = "DD-CAT-PARENT-MESSAGE-ID";
    public static final String CAT_HTTP_HEADER_ROOT_MESSAGE_ID = "DD-CAT-ROOT-MESSAGE-ID";
}

埋点时,在调用API的HttpClient工具类中统一增加代码,以GET方式为例:

public class HttpClientUtil {
    public static String doGet(String url) throws IOException {
        HttpGet httpGet = new HttpGet(url);
        CloseableHttpResponse response = null;
        CloseableHttpClient httpClient = HttpClientBuilder.create().build();
        String content = null;
        Transaction t = Cat.newTransaction(CatConstants.TYPE_CALL, url);
        try {
            CatContext context = ContextWarehouse.getContext();
            httpGet.setHeader(CatHttpConstants.CAT_HTTP_HEADER_ROOT_MESSAGE_ID, context.getProperty(Cat.Context.ROOT));
            httpGet.setHeader(CatHttpConstants.CAT_HTTP_HEADER_PARENT_MESSAGE_ID, context.getProperty(Cat.Context.PARENT));
            httpGet.setHeader(CatHttpConstants.CAT_HTTP_HEADER_CHILD_MESSAGE_ID, context.getProperty(Cat.Context.CHILD));
    
            response = httpClient.execute(httpGet);
            if (response.getStatusLine().getStatusCode() == 200) {
                content = EntityUtils.toString(response.getEntity(), "UTF-8");
                t.setStatus(Transaction.SUCCESS);
            }
        } catch (Exception e) {
            Cat.logError(e);
            t.setStatus(e);
            throw e;
        } finally {
            if (response != null) {
                response.close();
            }
            if (httpClient != null) {
                httpClient.close();
            }
            t.complete();
        }
        return content;
    }
}

异步请求实例

下面写一个异步请求的实例,通过多个商品ID异步获取对应的商品详细信息:

public class ProductService {

    /**
     * 声明一个大小固定为10的线程池
     */
    private static ExecutorService executor = Executors.newFixedThreadPool(10);

    /**
     * 通过商品ID列表异步获取对应的商品详细信息
     *
     * @param productIds 商品ID列表
     * @return 对应的商品详细信息
     */
    public List<String> findProductInfo(List<Long> productIds) {
        List<Future<String>> futures = new ArrayList<>();
        for (Long productId : productIds) {
            futures.add(executor.submit(new DdCallable(() -> {
                try {
                    //调用获取商品详细信息的API
                    return HttpClientUtil.doGet("http://api.product/get?id=" + productId);
                } catch (Exception e) {
                    return "";
                }
            })));
        }

        List<String> productInfos = new ArrayList<>();
        for (Future<String> future : futures) {
            try {
                //异步获取对应商品详细信息
                productInfos.add(future.get());
            } catch (Exception e) {
                productInfos.add("");
            }
        }
        return productInfos;
    }
}

这样写以后,在CAT服务端的Transaction报表中就可以查看到异步请求了。

相关文章
|
5月前
.NetCore 接口请求耗时记录
.NetCore 接口请求耗时记录
50 0
|
7月前
|
前端开发 开发工具 git
大事件项目15----axios响应拦截器,统一判断401做被动退出
大事件项目15----axios响应拦截器,统一判断401做被动退出
|
6月前
|
前端开发 JavaScript
Promise 等待多个接口返回数据再执行操作
Promise 等待多个接口返回数据再执行操作
61 0
|
8月前
|
iOS开发
Object c事件链传递
Object c事件链传递
70 1
|
7月前
|
JavaScript 前端开发
在vue中循环中调用接口-promise.all();按顺序执行异步处理
在vue中循环中调用接口-promise.all();按顺序执行异步处理
|
程序员 Scala 开发者
函数(方法)的调用机制|学习笔记
快速学习函数(方法)的调用机制。
114 0
函数(方法)的调用机制|学习笔记
|
网络协议 测试技术 Go
方法的调用和传参机制 | 学习笔记
快速学习方法的调用和传参机制
方法的调用和传参机制 | 学习笔记
|
Java
CheerpJ调用的两种方式
CheerpJ调用的两种方式
255 0
|
前端开发
手写promise异步状态修改then方法返回来的结果
手写promise异步状态修改then方法返回来的结果
手写promise异步状态修改then方法返回来的结果
|
前端开发
ES6 - Axios 全局设置取消请求及中断 Promise 调用链
ES6 - Axios 全局设置取消请求及中断 Promise 调用链
313 0