買買買結算系統
一年一度的雙十一購物狂歡節就要到了,又到剁手黨們開始表演的時刻了。當我們把種草很久的商品放入購物車以后,點擊“結算”按鈕時,就來到了買買買必不可少的結算頁面了。讓我們虛擬一個買買買結算系統,為結算頁面提供商品、促銷、庫存等結算信息,就此系統展開如何在SpringBoot項目中集成CAT調用鏈。買買買結算系統包含以下4個項目:
- 結算UI:為前端頁面提供結算的基礎數據。
- 購物車API:獲取用戶購物車中有哪些商品。
- 商品API:獲取商品詳細信息
- 商品促銷API:獲取商品的促銷信息
- 商品庫存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
然后,在每個項目里添加如下內容:
- 結算UI:
app.name=buy-buy-buy-checkout
- 購物車API:
app.name=buy-buy-buy-cart
- 商品API:
app.name=buy-buy-buy-product
- 商品促銷API:
app.name=buy-buy-buy-promotion
- 商品庫存API:
app.name=buy-buy-buy-store
注意:項目名稱只能包含英文字母 (a-z, A-Z)、數字 (0-9)、下划線 (_) 和中划線 (-)
准備埋點
在埋點之前,需要先寫兩個公共類,方便之后埋點時調用。
- 第一個類實現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 + '}';
}
}
- 第二個類中定義一些常量,在調用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進行分布式調用鏈監控,需要修改項目中的代碼進行埋點:
- 在剛剛接收到請求時進行埋點。
- 在准備調用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還有很多豐富的功能,可參見官網。