1. 程式人生 > >CAT中實現非同步請求的呼叫鏈檢視

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技術乾貨