1. 程式人生 > >五分鐘後,你將學會在SpringBoot專案中如何整合CAT呼叫鏈

五分鐘後,你將學會在SpringBoot專案中如何整合CAT呼叫鏈

買買買結算系統

一年一度的雙十一購物狂歡節就要到了,又到剁手黨們開始表演的時刻了。當我們把種草很久的商品放入購物車以後,點選“結算”按鈕時,就來到了買買買必不可少的結算頁面了。讓我們虛擬一個買買買結算系統,為結算頁面提供商品、促銷、庫存等結算資訊,就此係統展開如何在SpringBoot專案中整合CAT呼叫鏈。買買買結算系統包含以下4個專案:

  1. 結算UI:為前端頁面提供結算的基礎資料。
  2. 購物車API:獲取使用者購物車中有哪些商品。
  3. 商品API:獲取商品詳細資訊
  4. 商品促銷API:獲取商品的促銷資訊
  5. 商品庫存API:獲取商品的庫存資訊

時序圖如下:

通過Maven新增依賴

<dependency>
    <groupId>com.dianping.cat</groupId>
    <artifactId>cat-client</artifactId>
    <version>3.0.0</version>
</dependency>

配置client.xml

建立/data/appdatas/cat/目錄,並建立client.xml檔案:

<?xml version="1.0" encoding="utf-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema" xsi:noNamespaceSchemaLocation="config.xsd">
    <servers>
        <server ip="127.0.0.1" port="2280" http-port="8080" />
    </servers>
</config>

注意:把127.0.0.1替換成CAT服務端的IP。

配置專案名稱

買買買結算系統中一共有5個專案,每個專案都需要配置各自的專案名稱。
首先,在每個專案中建立如下檔案:
src/main/resources/META-INF/app.properties
然後,在每個專案裡新增如下內容:

  1. 結算UI:
    app.name=buy-buy-buy-checkout
  2. 購物車API:
    app.name=buy-buy-buy-cart
  3. 商品API:
    app.name=buy-buy-buy-product
  4. 商品促銷API:
    app.name=buy-buy-buy-promotion
  5. 商品庫存API:
    app.name=buy-buy-buy-store

注意:專案名稱只能包含英文字母 (a-z, A-Z)、數字 (0-9)、下劃線 (_) 和中劃線 (-)

準備埋點

在埋點之前,需要先寫兩個公共類,方便之後埋點時呼叫。

  1. 第一個類實現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 + '}';
    }
}
  1. 第二個類中定義一些常量,在呼叫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";
}

開始埋點

使用CAT進行分散式呼叫鏈監控,需要修改專案中的程式碼進行埋點:

  1. 在剛剛接收到請求時進行埋點。
  2. 在準備呼叫API時進行埋點。

那麼在買買買結算系統中需要做哪些程式碼修改呢?看一下時序圖的變化就明白了:

第一個埋點,在剛剛接收到請求時,這裡使用Filter實現,程式碼如下:

public class CatServletFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) servletRequest;

        CatContext catContext = new CatContext();
        catContext.addProperty(Cat.Context.ROOT, request.getHeader(CatHttpConstants.CAT_HTTP_HEADER_ROOT_MESSAGE_ID));
        catContext.addProperty(Cat.Context.PARENT, request.getHeader(CatHttpConstants.CAT_HTTP_HEADER_PARENT_MESSAGE_ID));
        catContext.addProperty(Cat.Context.CHILD, request.getHeader(CatHttpConstants.CAT_HTTP_HEADER_CHILD_MESSAGE_ID));
        Cat.logRemoteCallServer(catContext);

        Transaction t = Cat.newTransaction(CatConstants.TYPE_URL, request.getRequestURI());

        try {
            
            Cat.logEvent("Service.method", request.getMethod(), Message.SUCCESS, request.getRequestURL().toString());
            Cat.logEvent("Service.client", request.getRemoteHost());

            filterChain.doFilter(servletRequest, servletResponse);

            t.setStatus(Transaction.SUCCESS);
        } catch (Exception ex) {
            t.setStatus(ex);
            Cat.logError(ex);
            throw ex;
        } finally {
            t.complete();
        }
    }

    @Override
    public void destroy() {

    }
}

Filter已經寫好了,接下來還需要把Filter註冊到SpringBoot中:

@Configuration
public class CatConfiguration {
    @Bean
    public FilterRegistrationBean catServletFilter() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        CatServletFilter filter = new CatServletFilter();
        registration.setFilter(filter);
        registration.addUrlPatterns("/*");
        registration.setName("cat-servlet-filter");
        registration.setOrder(1);
        return registration;
    }
}

第兩個埋點,在呼叫API的HttpClient工具類中統一增加程式碼,以GET方式為例:

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 {
        Context ctx = new CatContext();
        Cat.logRemoteCallClient(ctx);
        httpGet.setHeader(CatHttpConstants.CAT_HTTP_HEADER_ROOT_MESSAGE_ID, ctx.getProperty(Cat.Context.ROOT));
        httpGet.setHeader(CatHttpConstants.CAT_HTTP_HEADER_PARENT_MESSAGE_ID, ctx.getProperty(Cat.Context.PARENT));
        httpGet.setHeader(CatHttpConstants.CAT_HTTP_HEADER_CHILD_MESSAGE_ID, ctx.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;
}

結語

以上便是SpringBoot整合CAT呼叫鏈的整個例項了,可以靈活應用,更加優雅的整合到實際專案中。
另外,CAT還有很多豐富的功能,可參見官網。