熬夜之作:一文帶你了解Cat分布式監(jiān)控
Cat 是什么?
CAT(Central Application Tracking)是基于 Java 開發(fā)的實時應(yīng)用監(jiān)控平臺,包括實時應(yīng)用監(jiān)控,業(yè)務(wù)監(jiān)控。
CAT 作為服務(wù)端項目基礎(chǔ)組件,提供了 Java, C/C++, Node.js, Python, Go 等多語言客戶端,已經(jīng)在美團點評的基礎(chǔ)架構(gòu)中間件框架(MVC 框架,RPC 框架,數(shù)據(jù)庫框架,緩存框架等,消息隊列,配置系統(tǒng)等)深度集成,為美團點評各業(yè)務(wù)線提供系統(tǒng)豐富的性能指標、健康狀況、實時告警等。
CAT 很大的優(yōu)勢是它是一個實時系統(tǒng),CAT 大部分系統(tǒng)是分鐘級統(tǒng)計,但是從數(shù)據(jù)生成到服務(wù)端處理結(jié)束是秒級別,秒級定義是 48 分鐘 40 秒,基本上看到 48 分鐘 38 秒數(shù)據(jù),整體報表的統(tǒng)計粒度是分鐘級;第二個優(yōu)勢,監(jiān)控數(shù)據(jù)是全量統(tǒng)計,客戶端預(yù)計算;鏈路數(shù)據(jù)是采樣計算。
Github: https://github.com/dianping/cat[1]
Cat 功能亮點
-
實時處理:信息的價值會隨時間銳減,尤其是事故處理過程中 -
全量數(shù)據(jù):全量采集指標數(shù)據(jù),便于深度分析故障案例 -
高可用:故障的還原與問題定位,需要高可用監(jiān)控來支撐 -
故障容忍:故障不影響業(yè)務(wù)正常運轉(zhuǎn)、對業(yè)務(wù)透明 -
高吞吐:海量監(jiān)控數(shù)據(jù)的收集,需要高吞吐能力做保證 -
可擴展:支持分布式、跨 IDC 部署,橫向擴展的監(jiān)控系統(tǒng)
為什么要用 Cat?
場景一:用戶反饋 App 無法下單,用戶反饋無法支付,用戶反饋商品無法搜索等問題
場景一的問題在于當(dāng)系統(tǒng)出現(xiàn)問題后,第一反饋的總是用戶。我們需要做的是什么,是在出問題后研發(fā)第一時間知曉,而不是讓用戶來告訴我們出問題了。
Cat 可以出故障后提供秒級別的異常告警機制,不用再等用戶來反饋問題了。
場景二:出故障后如何快速定位問題
一般傳統(tǒng)的方式當(dāng)出現(xiàn)問題后,我們就會去服務(wù)器上看看服務(wù)是否還存活。如果存活就會看看日志是否有異常信息。
在 Cat 后臺的首頁,會展示各個系統(tǒng)的運行情況,如果有異常,則會大片飄紅,非常明顯。最直接的方式還是直接查看 Problem 報表,這里會為我們展示直接的異常信息,快速定位問題。

場景三:用戶反饋訂單列表要 10 幾秒才展示,用戶反饋下單一直在轉(zhuǎn)圈圈
場景三屬于優(yōu)化相關(guān),對于研發(fā)來說,優(yōu)化是一個長期的過程,沒有最好只有更好。優(yōu)化除了需要有對應(yīng)的方案,最重要的是要對癥下藥。
所謂的對癥下藥也就是在優(yōu)化之前,你得先知道哪里比較慢。RPC 調(diào)用慢?數(shù)據(jù)庫查詢慢?緩存更新慢?
Cat 可以提供詳細的性能數(shù)據(jù),95 線,99 線等。更細粒度的就是可以看到某個請求或者某個業(yè)務(wù)方法的所有耗時邏輯,前提是你做了埋點操作。
Cat 報表
Cat 目前有五種報表,每種都有特定的應(yīng)用場景,下面我們來具體聊聊這些報表的作用。
Transaction 報表
適用于監(jiān)控一段代碼運行情況,比如:運行次數(shù)、QPS、錯誤次數(shù)、失敗率、響應(yīng)時間統(tǒng)計(平均影響時間、Tp 分位值)等等場景。
埋點方式:
public void shopService() {
Transaction transaction = Cat.newTransaction("ShopService", "Service");
try {
service();
transaction.setStatus(Transaction.SUCCESS);
} catch (Exception e) {
transaction.setStatus(e); // catch 到異常,設(shè)置狀態(tài),代表此請求失敗
Cat.logError(e); // 將異常上報到cat上
// 也可以選擇向上拋出:throw e;
} finally {
transaction.complete();
}
}
可以在基礎(chǔ)框架中對 Rpc, 數(shù)據(jù)庫等框架進行埋點,這樣就可以通過 Cat 來監(jiān)控這些組件了。
業(yè)務(wù)中需要埋點也可以使用 Cat 的 Transaction,比如下單,支付等核心功能,通常我們對 URL 進行埋點就可以了,也就包含了具體的業(yè)務(wù)流程。

Event 報表
適用于監(jiān)控一段代碼運行次數(shù),比如記錄程序中一個事件記錄了多少次,錯誤了多少次。Event 報表的整體結(jié)構(gòu)與 Transaction 報表幾乎一樣,只缺少響應(yīng)時間的統(tǒng)計。
埋點方式:
Cat.logEvent("Func", "Func1");

Problem 報表
Problem 記錄整個項目在運行過程中出現(xiàn)的問題,包括一些異常、錯誤、訪問較長的行為。
如果有人反饋你的接口報 500 錯誤了,你進 Cat 后就直接可以去 Problem 報表了,錯誤信息就在 Problem 中。

Problem 報表不需要手動埋點,我們只需要在項目中集成日志的 LogAppender 就可以將所有 error 異常記錄,下面的段落中會講解如何整合 LogAppender。
Heartbeat 報表
Heartbeat 報表是 CAT 客戶端,以一分鐘為周期,定期向服務(wù)端匯報當(dāng)前運行時候的一些狀態(tài)。

系統(tǒng)指標有系統(tǒng)的負載信息,內(nèi)存使用情況,磁盤使用情況等。
JVM 指標有 GC 相關(guān)信息,線程相關(guān)信息。
Business 報表
Business 報表對應(yīng)著業(yè)務(wù)指標,比如訂單指標。與 Transaction、Event、Problem 不同,Business 更偏向于宏觀上的指標,另外三者偏向于微觀代碼的執(zhí)行情況。
這個報表我也沒怎么用過,用的多的還是前面幾個。

Cat 在 Kitty Cloud 中的應(yīng)用
Kitty Cloud 的基礎(chǔ)組件是 Kitty,Kitty 里面對需要的一些框架都進行了一層包裝,比如擴展,增加 Cat 埋點之類的功能。
Cat 的集成
Kitty 中對 Cat 封裝了一層,在使用的時候直接依賴 kitty-spring-cloud-starter-cat 即可整合 Cat 到項目中。
<dependency>
<groupId>com.cxytiandi</groupId>
<artifactId>kitty-spring-cloud-starter-cat</artifactId>
<version>Kitty Version</version>
</dependency>
然后在 application 配置文件中配置 Cat 的服務(wù)端地址信息,多個英文逗號分隔:
cat.servers=47.105.66.210
在項目的 resources 目錄下創(chuàng)建 META-INF 目錄,然后在 META-INF 中創(chuàng)建 app.properties 文件配置 app.name。此名稱是在 Cat 后臺顯示的應(yīng)用名
app.name=kitty-cloud-comment-provider
最后需要配置一下 Cat 的 LogAppender,這樣應(yīng)用在記錄 error 級別的日志時,Cat 可以及時進行異常告警操作。
在 logback.xml 增加下面的配置:
<appender name="CatAppender" class="com.dianping.cat.logback.CatLogbackAppender"></appender>
<root level="INFO">
<appender-ref ref="CatAppender" />
</root>
更詳細的內(nèi)容請移步 Cat 的 Github 主頁進行查看。
MVC 框架埋點
基于 Spring Boot 做 Web 應(yīng)用開發(fā),我們最常用到的一個 Starter 包就是 spring-boot-starter-web。
如果你使用了 Kitty 來構(gòu)建微服務(wù)的框架,那么就不再需要直接依賴 spring-boot-starter-web。而是需要依賴 Kitty 中的 kitty-spring-cloud-starter-web。
kitty-spring-cloud-starter-web 在 spring-boot-starter-web 的基礎(chǔ)上進行了封裝,會對請求的 Url 進行 Cat 埋點,會對一些通用信息進行接收透傳,會對 RestTemplate 的調(diào)用進行 Cat 埋點。
在項目中依賴 kitty-spring-cloud-starter-web:
<dependency>
<groupId>com.cxytiandi</groupId>
<artifactId>kitty-spring-cloud-starter-web</artifactId>
<version>Kitty Version</version>
</dependency>
啟動項目,然后訪問你的 REST API??梢栽?Cat 的控制臺看到 URL 的監(jiān)控信息。

點擊 URL 進去可以看到具體的 URL 信息。

再進一步可以看到整個 URL 的信息,比如數(shù)據(jù)庫的查詢,緩存的操作,Http 的調(diào)用等。后端同學(xué)在優(yōu)化性能的時候就直接從 URL 下手可以將整個請求的鏈路耗時的情況都分析清楚。

Mybatis 埋點
Kitty 中 Mybatis 是用的 Mybatis Plus, 主要是對數(shù)據(jù)庫相關(guān)操作的 SQL 進行了 Cat 埋點,可以很方便的查看 SQL 的耗時情況。
依賴 kitty-spring-cloud-starter-mybatis:
<dependency>
<groupId>com.cxytiandi</groupId>
<artifactId>kitty-spring-cloud-starter-mybatis</artifactId>
<version>Kitty Version</version>
</dependency>
其他的使用方式還是跟 Mybatis Plus 一樣,具體參考 Mybatis Plus 文檔:https://mp.baomidou.com[2]
只要涉及到數(shù)據(jù)庫的操作,都會在 Cat 中進行數(shù)據(jù)的展示。

點擊 SQL 進去還可以看到是哪個 Mapper 的操作。

再進一步就可以看到具體的 SQL 語句和消耗的時間。

有了這些數(shù)據(jù),后端研發(fā)同學(xué)就可以對相關(guān)的 SQL 進行優(yōu)化了。
Redis 埋點
如果需要使用 Spring Data Redis 的話,直接集成 kitty-spring-cloud-starter-redis 就可以,kitty-spring-cloud-starter-redis 中對 Redis 的命令進行了埋點,可以在 Cat 上直觀的查看對應(yīng)的命令和消耗的時間。
添加對應(yīng)的 Maven 依賴:
<dependency>
<groupId>com.cxytiandi</groupId>
<artifactId>kitty-spring-cloud-starter-redis</artifactId>
<version>Kitty Version</version>
</dependency>
直接使用 StringRedisTemplate:
@Autowired
private StringRedisTemplate stringRedisTemplate;
stringRedisTemplate.opsForValue().set("name", "yinjihuan");
Cat 中可以看到 Redis 信息。

點擊 Redis 進去可以看到有哪些命令。

再進去可以看到命令的詳細信息,比如操作的 key 和消耗的時間。

MongoDB 埋點
Kitty 中對 Spring Data Mongodb 做了封裝,只對 MongoTemplate 做了埋點。使用時需要依賴 kitty-spring-cloud-starter-mongodb。
<dependency>
<groupId>com.cxytiandi</groupId>
<artifactId>kitty-spring-cloud-starter-mongodb</artifactId>
<version>Kitty Version</version>
</dependency>
在發(fā)生 Mongo 的操作后,Cat 上就可以看到相關(guān)的數(shù)據(jù)了。

點進去就可以看到是 MongoTemplate 的哪個方法發(fā)生了調(diào)用。

再進一步就可以看到具體的 Mongo 參數(shù)和消耗的時間。

還有 Dubbo, Feign,Jetcache,ElasticSearch 等框架的埋點就不細講了,感興趣的可以移步 Github 查看代碼。
Cat 使用小技巧
埋點工具類
如果要對業(yè)務(wù)方法進行監(jiān)控,我們一般會用 Transaction 功能,將業(yè)務(wù)邏輯包含在 Transaction 里面,就能監(jiān)控這個業(yè)務(wù)的耗時信息。
埋點的方式也是通過 Cat.newTransaction 來進行,具體可以參考上面 Transaction 介紹時給出的埋點示列。
像這種埋點的方式最好是有一個統(tǒng)一的工具類去做,將埋點的細節(jié)封裝起來。
public class CatTransactionManager {
public static <T> T newTransaction(Supplier<T> function, String type, String name, Map<String, Object> data) {
Transaction transaction = Cat.newTransaction(type, name);
if (data != null && !data.isEmpty()) {
data.forEach(transaction::addData);
}
try {
T result = function.get();
transaction.setStatus(Message.SUCCESS);
return result;
} catch (Exception e) {
Cat.logError(e);
if (e.getMessage() != null) {
Cat.logEvent(type + "_" + name + "_Error", e.getMessage());
}
transaction.setStatus(e);
throw e;
} finally {
transaction.complete();
}
}
}
工具類使用:
public SearchResponse search(SearchRequest searchRequest, RequestOptions options) {
Map<String, Object> catData = new HashMap<>(1);
catData.put(ElasticSearchConstant.SEARCH_REQUEST, searchRequest.toString());
return CatTransactionManager.newTransaction(() -> {
try {
return restHighLevelClient.search(searchRequest, options);
} catch (IOException e) {
throw new RuntimeException(e);
}
}, ElasticSearchConstant.ES_CAT_TYPE, ElasticSearchConstant.SEARCH, catData);
}
通過使用工具類,不再需要每個監(jiān)控的地方都是設(shè)置 Transaction 是否 complete,是否成功這些信息了。
注解埋點
為了讓 Transaction 使用更方便,我們可以自定義注解來做這個事情。比如需要監(jiān)控下單,支付等核心業(yè)務(wù)方法,那么就可以使用自定義的 Transaction 注解加在方法上,然后通過 AOP 去統(tǒng)一做監(jiān)控。
定義注解:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface CatTransaction {
/**
* 類型, 默認為Method
* @return
*/
String type() default "";
/**
* 名稱, 默認為類名.方法名
* @return
*/
String name() default "";
/**
* 是否保存參數(shù)信息到Cat
* @return
*/
boolean isSaveParamToCat() default true;
}
定義切面:
@Aspect
public class CatTransactionAspect {
@Around("@annotation(catTransaction)")
public Object aroundAdvice(ProceedingJoinPoint joinpoint, CatTransaction catTransaction) throws Throwable {
String type = catTransaction.type();
if (StringUtils.isEmpty(type)){
type = CatConstantsExt.METHOD;
}
String name = catTransaction.name();
if (StringUtils.isEmpty(name)){
name = joinpoint.getSignature().getDeclaringType().getSimpleName() + "." + joinpoint.getSignature().getName();
}
Map<String, Object> data = new HashMap<>(1);
if (catTransaction.isSaveParamToCat()) {
Object[] args = joinpoint.getArgs();
if (args != null) {
data.put("params", JsonUtils.toJson(args));
}
}
return CatTransactionManager.newTransaction(() -> {
try {
return joinpoint.proceed();
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
}, type, name, data);
}
}
注解使用:
@CatTransaction
@Override
public Page<ArticleIndexBO> searchArticleIndex(ArticleIndexSearchParam param) {
}
你可能關(guān)心的幾個問題
Cat 能做鏈路跟蹤嗎?
Cat 主要是一個實時監(jiān)控系統(tǒng),并不是一個標準的全鏈路系統(tǒng),主要是 Cat 的 logview 在異步線程等等一些場景下,不太合適,Cat 本身模型并不適合這個。Cat 的 Github 上有說明:在美團點評內(nèi)部,有 mtrace 專門做全鏈路分析。
但是如果在 Mvc,遠程調(diào)用等這些框架中做好了數(shù)據(jù)的無縫傳輸,Cat 也可以充當(dāng)一個鏈路跟蹤的系統(tǒng),基本的場景足夠了。
Cat 也可以構(gòu)建遠程消息樹,可以看到請求經(jīng)過了哪些服務(wù),每個服務(wù)的耗時等信息。只不過服務(wù)之間的依賴關(guān)系圖在 Cat 中沒有。
下圖請求從網(wǎng)關(guān)進行請求轉(zhuǎn)發(fā)到 articles 上面,然后 articles 里面調(diào)用了 users 的接口。

Cat 跟 Skywalking 哪個好用?
Skywalking 也是一款非常優(yōu)秀的 APM 框架,我還沒用過,不過看過一些文檔,功能點挺全的 ,界面也挺好看。最大的優(yōu)勢是不用像 Cat 一樣需要埋點,使用字節(jié)碼增強的方式來對應(yīng)用進行監(jiān)控。
之所以列出這個小標題是因為如果大家還沒有用的話肯定會糾結(jié)要選擇落地哪個去做監(jiān)控。我個人認為這兩個都可以,可以自己先弄個簡單的版本體驗體驗,結(jié)合你想要的功能點來評估落地哪個。
用 Cat 的話最好有一套基礎(chǔ)框架,在基礎(chǔ)框架中埋好點,這樣才能在 Cat 中詳細的顯示各種信息來幫助我們快速定位問題和優(yōu)化性能。
參考資料
cat: https://github.com/dianping/cat
[2]mybatisPlus: https://mp.baomidou.com
[3]kitty-cloud: https://github.com/yinjihuan/kitty-cloud
特別推薦一個分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒關(guān)注的小伙伴,可以長按關(guān)注一下:
長按訂閱更多精彩▼
如有收獲,點個在看,誠摯感謝
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!