熬夜之作:一文帶你瞭解Cat分散式監控

猿天地發表於2020-06-08

Cat 是什麼?

CAT(Central Application Tracking)是基於 Java 開發的實時應用監控平臺,包括實時應用監控,業務監控。

CAT 作為服務端專案基礎元件,提供了 Java, C/C++, Node.js, Python, Go 等多語言客戶端,已經在美團點評的基礎架構中介軟體框架(MVC 框架,RPC 框架,資料庫框架,快取框架等,訊息佇列,配置系統等)深度整合,為美團點評各業務線提供系統豐富的效能指標、健康狀況、實時告警等。

CAT 很大的優勢是它是一個實時系統,CAT 大部分系統是分鐘級統計,但是從資料生成到服務端處理結束是秒級別,秒級定義是 48 分鐘 40 秒,基本上看到 48 分鐘 38 秒資料,整體報表的統計粒度是分鐘級;第二個優勢,監控資料是全量統計,客戶端預計算;鏈路資料是取樣計算。

Github: https://github.com/dianping/cat

Cat 功能亮點

  • 實時處理:資訊的價值會隨時間銳減,尤其是事故處理過程中
  • 全量資料:全量採集指標資料,便於深度分析故障案例
  • 高可用:故障的還原與問題定位,需要高可用監控來支撐
  • 故障容忍:故障不影響業務正常運轉、對業務透明
  • 高吞吐:海量監控資料的收集,需要高吞吐能力做保證
  • 可擴充套件:支援分散式、跨 IDC 部署,橫向擴充套件的監控系統

為什麼要用 Cat?

場景一:使用者反饋 App 無法下單,使用者反饋無法支付,使用者反饋商品無法搜尋等問題

場景一的問題在於當系統出現問題後,第一反饋的總是使用者。我們需要做的是什麼,是在出問題後研發第一時間知曉,而不是讓使用者來告訴我們出問題了。

Cat 可以出故障後提供秒級別的異常告警機制,不用再等使用者來反饋問題了。

場景二:出故障後如何快速定位問題

一般傳統的方式當出現問題後,我們就會去伺服器上看看服務是否還存活。如果存活就會看看日誌是否有異常資訊。

在 Cat 後臺的首頁,會展示各個系統的執行情況,如果有異常,則會大片飄紅,非常明顯。最直接的方式還是直接檢視 Problem 報表,這裡會為我們展示直接的異常資訊,快速定位問題。

場景三:使用者反饋訂單列表要 10 幾秒才展示,使用者反饋下單一直在轉圈圈

場景三屬於優化相關,對於研發來說,優化是一個長期的過程,沒有最好只有更好。優化除了需要有對應的方案,最重要的是要對症下藥。

所謂的對症下藥也就是在優化之前,你得先知道哪裡比較慢。RPC 呼叫慢?資料庫查詢慢?快取更新慢?

Cat 可以提供詳細的效能資料,95 線,99 線等。更細粒度的就是可以看到某個請求或者某個業務方法的所有耗時邏輯,前提是你做了埋點操作。

Cat 報表

Cat 目前有五種報表,每種都有特定的應用場景,下面我們來具體聊聊這些報表的作用。

Transaction 報表

適用於監控一段程式碼執行情況,比如:執行次數、QPS、錯誤次數、失敗率、響應時間統計(平均影響時間、Tp 分位值)等等場景。

埋點方式:

public void shopService() {
    Transaction transaction = Cat.newTransaction("ShopService", "Service");
    try {
        service();
        transaction.setStatus(Transaction.SUCCESS);
    } catch (Exception e) {
        transaction.setStatus(e); // catch 到異常,設定狀態,代表此請求失敗
        Cat.logError(e); // 將異常上報到cat上
        // 也可以選擇向上丟擲: throw e;
    } finally {
        transaction.complete();
    }
}

可以在基礎框架中對 Rpc, 資料庫等框架進行埋點,這樣就可以通過 Cat 來監控這些元件了。

業務中需要埋點也可以使用 Cat 的 Transaction,比如下單,支付等核心功能,通常我們對 URL 進行埋點就可以了,也就包含了具體的業務流程。

Event 報表

適用於監控一段程式碼執行次數,比如記錄程式中一個事件記錄了多少次,錯誤了多少次。Event 報表的整體結構與 Transaction 報表幾乎一樣,只缺少響應時間的統計。

埋點方式:

 Cat.logEvent("Func", "Func1");

Problem 報表

Problem 記錄整個專案在執行過程中出現的問題,包括一些異常、錯誤、訪問較長的行為。

如果有人反饋你的介面報 500 錯誤了,你進 Cat 後就直接可以去 Problem 報表了,錯誤資訊就在 Problem 中。

Problem 報表不需要手動埋點,我們只需要在專案中整合日誌的 LogAppender 就可以將所有 error 異常記錄,下面的段落中會講解如何整合 LogAppender。

Heartbeat 報表

Heartbeat 報表是 CAT 客戶端,以一分鐘為週期,定期向服務端彙報當前執行時候的一些狀態。

系統指標有系統的負載資訊,記憶體使用情況,磁碟使用情況等。

JVM 指標有 GC 相關資訊,執行緒相關資訊。

Business 報表

Business 報表對應著業務指標,比如訂單指標。與 Transaction、Event、Problem 不同,Business 更偏向於巨集觀上的指標,另外三者偏向於微觀程式碼的執行情況。

這個報表我也沒怎麼用過,用的多的還是前面幾個。

Cat 在 Kitty Cloud 中的應用

Kitty Cloud 的基礎元件是 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 的服務端地址資訊,多個英文逗號分隔:

cat.servers=47.105.66.210

在專案的 resources 目錄下建立 META-INF 目錄,然後在 META-INF 中建立 app.properties 檔案配置 app.name。此名稱是在 Cat 後臺顯示的應用名

app.name=kitty-cloud-comment-provider

最後需要配置一下 Cat 的 LogAppender,這樣應用在記錄 error 級別的日誌時,Cat 可以及時進行異常告警操作。

在 logback.xml 增加下面的配置:

 <appender name="CatAppender" class="com.dianping.cat.logback.CatLogbackAppender"></appender>
 <root level="INFO">
     <appender-ref ref="CatAppender" />
 </root>

更詳細的內容請移步 Cat 的 Github 主頁進行檢視。

MVC 框架埋點

基於 Spring Boot 做 Web 應用開發,我們最常用到的一個 Starter 包就是 spring-boot-starter-web。

如果你使用了 Kitty 來構建微服務的框架,那麼就不再需要直接依賴 spring-boot-starter-web。而是需要依賴 Kitty 中的 kitty-spring-cloud-starter-web。

kitty-spring-cloud-starter-web 在 spring-boot-starter-web 的基礎上進行了封裝,會對請求的 Url 進行 Cat 埋點,會對一些通用資訊進行接收透傳,會對 RestTemplate 的呼叫進行 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 的監控資訊。

點選 URL 進去可以看到具體的 URL 資訊。

再進一步可以看到整個 URL 的資訊,比如資料庫的查詢,快取的操作,Http 的呼叫等。後端同學在優化效能的時候就直接從 URL 下手可以將整個請求的鏈路耗時的情況都分析清楚。

Mybatis 埋點

Kitty 中 Mybatis 是用的 Mybatis Plus, 主要是對資料庫相關操作的 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

只要涉及到資料庫的操作,都會在 Cat 中進行資料的展示。

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

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

有了這些資料,後端研發同學就可以對相關的 SQL 進行優化了。

Redis 埋點

如果需要使用 Spring Data Redis 的話,直接整合 kitty-spring-cloud-starter-redis 就可以,kitty-spring-cloud-starter-redis 中對 Redis 的命令進行了埋點,可以在 Cat 上直觀的檢視對應的命令和消耗的時間。

新增對應的 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>

在發生 Mongo 的操作後,Cat 上就可以看到相關的資料了。

點進去就可以看到是 MongoTemplate 的哪個方法發生了呼叫。

再進一步就可以看到具體的 Mongo 引數和消耗的時間。

還有 Dubbo, Feign,Jetcache,ElasticSearch 等框架的埋點就不細講了,感興趣的可以移步 Github 檢視程式碼。

Cat 使用小技巧

埋點工具類

如果要對業務方法進行監控,我們一般會用 Transaction 功能,將業務邏輯包含在 Transaction 裡面,就能監控這個業務的耗時資訊。

埋點的方式也是通過 Cat.newTransaction 來進行,具體可以參考上面 Transaction 介紹時給出的埋點示列。

像這種埋點的方式最好是有一個統一的工具類去做,將埋點的細節封裝起來。

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);
}

通過使用工具類,不再需要每個監控的地方都是設定 Transaction 是否 complete,是否成功這些資訊了。

註解埋點

為了讓 Transaction 使用更方便,我們可以自定義註解來做這個事情。比如需要監控下單,支付等核心業務方法,那麼就可以使用自定義的 Transaction 註解加在方法上,然後通過 AOP 去統一做監控。

定義註解:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface CatTransaction {
    /**
     * 型別, 預設為Method
     * @return
     */
    String type() default "";
    /**
     * 名稱, 預設為類名.方法名
     * @return
     */
    String name() default "";
    /**
     * 是否儲存引數資訊到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) {
}

你可能關心的幾個問題

Cat 能做鏈路跟蹤嗎?

Cat 主要是一個實時監控系統,並不是一個標準的全鏈路系統,主要是 Cat 的 logview 在非同步執行緒等等一些場景下,不太合適,Cat 本身模型並不適合這個。Cat 的 Github 上有說明:在美團點評內部,有 mtrace 專門做全鏈路分析。

但是如果在 Mvc,遠端呼叫等這些框架中做好了資料的無縫傳輸,Cat 也可以充當一個鏈路跟蹤的系統,基本的場景足夠了。

Cat 也可以構建遠端訊息樹,可以看到請求經過了哪些服務,每個服務的耗時等資訊。只不過服務之間的依賴關係圖在 Cat 中沒有。

下圖請求從閘道器進行請求轉發到 articles 上面,然後 articles 裡面呼叫了 users 的介面。

Cat 跟 Skywalking 哪個好用?

Skywalking 也是一款非常優秀的 APM 框架,我還沒用過,不過看過一些文件,功能點挺全的 ,介面也挺好看。最大的優勢是不用像 Cat 一樣需要埋點,使用位元組碼增強的方式來對應用進行監控。

之所以列出這個小標題是因為如果大家還沒有用的話肯定會糾結要選擇落地哪個去做監控。我個人認為這兩個都可以,可以自己先弄個簡單的版本體驗體驗,結合你想要的功能點來評估落地哪個。

用 Cat 的話最好有一套基礎框架,在基礎框架中埋好點,這樣才能在 Cat 中詳細的顯示各種資訊來幫助我們快速定位問題和優化效能。

感興趣的 Star 下唄:https://github.com/yinjihuan/kitty-cloud

相關文章