CAT中實現異步請求的調用鏈查看


CAT簡介

CAT(Central Application Tracking),是美團點評基於 Java 開發的一套開源的分布式實時監控系統。美團點評基礎架構部希望在基礎存儲、高性能通信、大規模在線訪問、服務治理、實時監控、容器化及集群智能調度等領域提供業界領先的、統一的解決方案,CAT 目前在美團點評的產品定位是應用層的統一監控組件,在中間件(RPC、數據庫、緩存、MQ 等)框架中得到廣泛應用,為各業務線提供系統的性能指標、健康狀況、實時告警等服務。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術干貨。

准備工作

對於同步請求API,CAT服務端自然是可以看到的。同步請求API的實例可以參考之前的文章《五分鍾后,你將學會在SpringBoot項目中如何集成CAT調用鏈》。但對於異步請求API,因為不在同一線程中,在子線程中無法獲取到父線程消息樹,所以在CAT服務端是無法看到的對應請求。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術干貨。

首先,寫一個類實現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();
    }
}

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術干貨。

定義一些常量,在調用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;
    }
}

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術干貨。

異步請求實例

下面寫一個異步請求的實例,通過多個商品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報表中就可以查看到異步請求了。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術干貨。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM