ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

CAT实时监控平台从零部署到Java客户端接入实践

CAT实时监控平台从零部署到Java客户端接入实践

排障搞到凌晨一点,接口的 P99 从 200 毫秒涨到 1.2 秒,线上查了一圈:MySQL 慢查询没发现、Redis 命中率正常、消息队列积压为零,就是找不到瓶颈。最后是某同事提议把链路里的每一个外部调用都拉出来看,逐一排查,才在一个旧网关的 HTTP 调用上抓到异常。这件事之后,团队决定把实时监控补上,选型时就盯上了开源社区里常被简称为 CAT 的实时监控平台。

这篇文章就是我在两套环境里把 CAT 从零装起来,并接进模拟项目 X 的全过程:服务端搭建的两种路线、Java 客户端接入的完整配置、埋点 API 怎么用,以及我实际踩过的六个坑。适合正在选型 APM、或者已经确定用 CAT 但卡在安装配置阶段的同学。先说清楚,CAT 不是那种开箱即看的玩具监控,装起来有点门槛,但只要把链路理顺,它在生产环境里能给你的排查效率带来质的提升。

1. 为什么我不选轻量 APM,而是选择了 CAT

1.1 CAT 到底解决了什么问题

CAT 的定位是实时应用监控平台,核心能力集中在三块:第一,分钟级别的实时性能报表,不用等聚合任务跑完,刷新页面就能看到最近几秒内的调用情况;第二,非常灵活的自定义埋点体系,可以精确到某个 URL、某个 SQL、某个缓存操作,甚至可以给业务埋点打上标签;第三,链路追踪与 Problem 定位,它能把一次请求经过的每个环节按树形展开,哪个节点耗时高一眼看穿。

这块能力和网上常见的轻量 APM 有重叠,但 CAT 更偏向"自己掌控数据":数据由客户端采集后直接上报到你的服务器,不经过任何第三方平台。对很多中小团队来说,监控数据留在自己手里,后续对接告警或者做二次分析都方便得多。而且它的告警能力是内置的,阈值、恢复、持续告警时间都可以自定义,不需要额外再搭一套规则引擎。

1.2 选型对比与适用边界

我建了个小表格,把当时纠结过的三类方案放在一起对比,各位可以代入自己的场景感受一下:

能力维度CAT轻量开源自建 APM商业监控产品
数据实时性秒级聚合,报表刷新快分钟级甚至更慢秒级,但依赖网络回传
自定义埋点能力强,API 灵活一般,通常只做自动埋点弱,基本靠预设
链路追踪深度支持,需要配合埋点习惯开箱即用,自动生成链路强,但偏黑盒
部署运维成本较高,依赖 MySQL、HDFS 选配较低,单进程就能跑零部署,按量付费
掌控度数据、规则、存储都在自己手里数据在自己手里,能力有限数据在别人手里

我们当时选 CAT 的判断依据其实很简单:团队是 Java 技术栈,有一个复杂的核心链路会经过多个内部服务,而且我们有明确的业务指标埋点诉求,比如 "下单转化率"、"支付回调延迟" 这类需要自定义维度的数据。轻量 APM 自带链路是方便,但自定义埋点能力太弱;商业产品又绕不开数据外送的问题。CAT 恰好把两边需求都覆盖了。

不过也要说清适用边界:如果你的目标仅仅是快速拿到一个漂亮的调用拓扑图,不想花太多精力维护服务端,那 CAT 对你来说是重了一半。它的安装和维护确实比轻量 APM 要繁琐,我在下面的第二章会讲到,它依赖 MySQL,集群模式下还可能用到 HDFS 做历史报表存储。这些都需要额外的运维精力去支撑。

2. 安装 CAT 之前,先花十分钟理清架构和端口

2.1 客户端、服务端、控制台各管什么

安装之前最怕的就是稀里糊涂把服务端跑起来,然后发现完全不知道自己在装什么。CAT 的整体运行逻辑其实可以拆成四块:

客户端模块(cat-client):以 JAR 包方式嵌进你的 Java 应用,负责埋点采集、本地聚合、异步上报。它会在应用内部维护一个缓冲队列,把埋点数据批量发到服务端,避免每次打点都发生网络交互。

服务端接收模块(cat-consumer):接收客户端上报的数据,做实时统计与分析,生成报表数据。多个节点可以组成集群,分担压力。

控制台模块(cat-home):承载 Web 界面,提供报表展示、告警管理、配置下发。通常和接收模块部署在同一进程里,一个 JVM 实例同时承担这两个角色。

存储层:MySQL 保存元数据和基础配置,比如机器信息、告警配置、报表定义;HDFS 是可选的,用于保存历史报表文件,如果不需要留存太久的数据,不装 HDFS 也能跑,只是报表保留时间会受限。

端口这块我专门记了一份小抄:

端口用途说明
8080控制台 Web 端口浏览器访问 dashboard 用的
2280客户端数据上报端口cat-client 往服务端打数据的 TCP 通道
3306MySQL 端口服务端连接数据库使用

这里最容易踩的坑是防火墙只放行了 8080,忘了 2280。服务端 Web 界面看着一切正常,但所有客户端都连不进来,报表数据完全为空,排查半天最后才发现是底层上报端口被防火墙挡了。所以装完第一件事就是确认 2280 端口能被客户端所在机器访问到。

2.2 环境准备清单

我准备了一张自检清单,在动手前过一遍,能省掉后面非常多麻烦:

  • 一台 Linux 服务器,内存 4G 起步。内存太小的话,控制台和接收模块跑在同一个 JVM 里容易触发频繁 GC,报表刷新很卡。
  • JDK 1.8 或更高版本,优先使用 JDK 8 匹配较为稳妥。装完执行java -version确认。
  • MySQL 5.7 或 8.0,准备一个空数据库,建议数据库字符集用utf8mb4,避免中文描述乱码。
  • 时间同步确认。执行date看下系统时间和实际时间是否一致,差太多会导致客户端上报的数据被归到错误的时间段,后续报表显示很难看。
  • 防火墙和安全组放行 8080、2280 这两个端口。

另外建议提前规划好存储位置。CAT 服务端运行时会读一个固定的数据目录,我用的是/data/appdatas/cat,里面放配置文件,还有日志。这个目录用户的权限一定给足,否则启动时静默报错,报表页面经常白屏。后面我在第五章里会细讲。

3. 服务端搭建:Docker 快装与源码部署两条线

3.1 Docker 快速搭建路线

如果只是搭一套测试环境先看效果,Docker 是最快的路线。我当时的做法是三步走。

第一步,准备 MySQL 并初始化数据库。我是在同一台机器上已经有一个 MySQL 实例,直接手动创建了一个名为cat的数据库:

CREATE DATABASE IF NOT EXISTS cat DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;

初始化表结构不需要自己写 SQL 脚本,CAT 服务端第一次启动时会自动完成建表。如果遇到权限受限无法建表,那就要拿官方仓库里的初始化脚本手动执行,这个后面再细说。

第二步,准备数据目录和配置文件。创建目录/data/appdatas/cat,在里面放一个datasources.xml告诉服务端怎么连数据库。内容大致是这样的:

<?xml version="1.0" encoding="utf-8"?> <data-source> <data-source id="default"> <url>jdbc:mysql://127.0.0.1:3306/cat</url> <user>cat</user> <password>your_password</password> </data-source> </data-source>

注意这个配置文件的格式不同版本会有一点差异,但总体的 XML 结构类似,核心就是指定 JDBC URL、用户名、密码。我当时直接在这里把待办事项漏了:账号的权限不够,导致启动时连不上库,控制台报 500。所以一定要在 MySQL 里先给这个账号赋上全库权限。

第三步,启动容器。我当时用的是社区维护的镜像,命令大概长这样:

docker run -d --name cat-server \ -p 8080:8080 -p 2280:2280 \ -v /data/appdatas/cat:/data/appdatas/cat \ -e CAT_HOME=/data/appdatas/cat \ registry.example.com/cat-server:3.0

这里-v把宿主机的数据目录挂载进容器,让配置文件和日志持久化,容器重启也不丢。启动后看日志确认没有异常,然后访问http://服务器IP:8080/cat,能出现欢迎页就说明服务端起来了。

3.2 源码编译部署路线

生产环境我更推荐源码部署,因为可以自己定制 JVM 参数、方便扩节点、也更容易排查问题。源码部署的流程也不复杂,核心步骤是:拉取源码、执行 Maven 构建、部署产物。

先准备好构建环境:JDK 1.8 加 Maven 3.x,确认mvn -v能正常输出。然后从官方仓库克隆代码,执行构建:

git clone <官方仓库地址> cat-src cd cat-src mvn clean install -DskipTests

构建完成后,在cat-home模块下会生成可部署的产物。我当时的做法是把cat-home的target目录里的 war 包直接丢到 Tomcat 的webapps下,然后把web.xml里指定的数据目录建立好,一样放/data/appdatas/cat,并写入datasources.xml。启动 Tomcat,等日志出现启动完成标志,访问控制台验证。

源码部署比 Docker 多出来的工作量主要是环境配置:Tomcat 的启动参数需要设大一点,尤其是堆内存。我一般给服务端分配至少 2G 的堆,采集中等规模的团队是完全够用的。另外集群模式时,我们需要确保每个节点都有独立的会话和路由,这个一般通过配置管理工具分发配置来实现。

3.3 启动验证与多节点扩展

无论是 Docker 还是源码方式,装完之后都建议按这个顺序快速验证:

  1. 登录控制台,进入管理页面,能看到当前服务端节点状态。
  2. 在服务器本机用telnet 127.0.0.1 2280测试一下端口通不通。
  3. 去客户端机器上再测一次telnet 服务器IP 2280,保证跨机器能通。

到这里服务端就绪了。如果以后流量大了需要扩成集群,思路也不复杂:再起几个同样的服务端节点,共同压到一个 MySQL 上,然后在客户端配置里把多个服务器 IP 都写上,CAT 客户端会自动做负载均衡和故障转移。HDFS 只有在需要长期保留历史报表时才值得引入,否则建议先不加,把架构保持得简单一些。

注意:Docker 路线里,容器一旦重建,/data/appdatas/cat 如果没挂载会丢配置,等于重新装一遍。生产环境强烈建议挂载宿主目录。

4. Java 客户端接入:从依赖到首条业务报表

4.1 加依赖和写 client.xml

服务端只是把"收数据的仓库"建好了,真正的核心是把客户端接进来。CAT 的 Java 客户端是通过 cat-client 这个 SDK 被集成到业务应用里的。在 Maven 中央仓库搜cat-client,把当前稳定版坐标加到pom.xml里即可。版本号一定要和服务端兼容,我当时因为版本不匹配,客户端日志反复报 JSON 解析异常,后来统一版本后才消停。

依赖加好之后,还要在应用的src/main/resources目录下放一个客户端配置文件。CAT 客户端默认会去找client.xml,内容需要指明服务端的地址:

<?xml version="1.0" encoding="utf-8"?> <config mode="client"> <servers> <server ip="192.168.1.100" port="2280" /> </servers> </config>

如果以后服务端做了集群,这里就继续加<server>节点。客户端本地上报数据失败时会先把日志写入本地缓存,等网络恢复后再补报,这在断网场景下能保留一些重要数据。不过补报机制依赖本地磁盘目录的写权限,通常 CAT 客户端会向CAT_HOME目录写日志,没有配置CAT_HOME的情况下默认写到用户目录的某个隐藏文件夹,需要留意磁盘空间。

4.2 三种最常用的埋点 API

依赖和配置文件搞定后,应用启动时就会自动上报 JVM 数据(比如内存、线程数、Class 加载数量等),这些不用写任何代码就能在控制台看到。但真正有价值的是自定义埋点,三个高频 API 我先列出来给大家。

Transaction 是核心,用来记录一段完整操作的耗时和状态。比如我处理一个下单请求,想看看整个方法花了多少时间,就可以这样打点:

Transaction t = Cat.newTransaction("URL", "/order/create"); try { // 你的业务逻辑 Thread.sleep(100); // 模拟耗时操作 t.setSuccessStatus(); } catch (Exception e) { Cat.logError(e); t.setStatus(e); } finally { t.complete(); }

newTransaction有两个参数,第一个是类型,第二个是名称,我习惯按"URL" 和具体路径来命名,这样在报表里能很清楚看到每个请求链路。注意complete()一定要在 finally 里调用,否则 Transaction 不会结束,报表这个节点会一直挂在那里。

Event 用来记录一次事件,比如 "某个缓存失败了"、"某个开关被打开了",它关注的是"发生了没有",而不是耗时:

Cat.logEvent("Cache", "Redis.getFailed");

Metric 用来做计数器,比如每分钟成功下单量:

Cat.logMetricForCount("order.create.success");

还有logMetricForDuration这类 API,可以记录某个操作的耗时平均值,适合做业务侧的 SLA 监控。总之一句话:Transaction 负责"耗时和状态",Event 负责"事件发生",Metric 负责"计数与数值统计",三者组合起来基本覆盖了所有监控诉求。

4.3 验证首条数据是否上报成功

接完客户端,启动应用,等大概一到两分钟,回到控制台找到"实时报表"或"客户端状态",如果配置正确,很快就能看到这个应用的主机名和 IP 出现在列表里。我习惯的做法是直接在代码里打一个 Test Transaction,然后去报表里搜这个名称,能搜到就说明链路全通了。

这里有个容易被忽略的点:首次部署时服务端可能还没有生成路由表,客户端向服务端上报了数据,但控制台要等下一轮路由刷新才能看到。看到没数据别急着重启服务,等两三分钟再看,一般就会出现了。这个我在下一章也会提到。

5. 踩过的六个坑,逐条复盘

5.1 首屏一直空白,问题出在数据目录权限

我第一次部署时,控制台登录成功,页面也能打开,但所有报表区域都是空白,等了十分钟还是空白。后来去翻服务端日志,发现大量目录创建失败相关的报错,根源是/data/appdatas/cat目录的所有者是 root,而服务端进程是以普通用户启动的。解决办法非常原始:chown -R 用户:用户组 /data/appdatas/cat,再重启服务端就好。这个坑之所以要特别强调,是因为页面没有任何明显的报错提示,新手很容易在日志里迷路。

5.2 客户端日志提示无法连接服务端,但其实不是网络问题

有一次客户端日志已经明确报"connect refused"了,但我测试端口是通的,抓破脑袋想不通。后来发现客户端配置里的 IP 写成了服务端的内网 IP,而客户端和服务端之间经过了一组防火墙规则,只放行了某个固定 IP。说白了还是配置里的 IP 不匹配。建议所有配置统一走内网域名,不要直接写 IP,后续扩容换机器时也方便。如果非得上报源 IP,一定要把网络路径提前画清楚。

5.3 服务器时间不同步,报表全部乱套

CAT 的数据都按小时和分钟聚合,客户端上报时会带上时间戳,服务端按这个时间戳归类。如果某台业务服务器的系统时间快了五分钟,那么这五分钟内的数据全部会被归到前一个时间段,而且里面告警规则经常会因为时间不同步产生误报。解决办法就是在所有机器上部署好 NTP 校时,并且在安装清单里把它当作必检项。

5.4 MySQL 连接池配置问题导致服务端频繁告警

CAT 服务端默认会对 MySQL 连接数做限制,而服务端在高峰期会并发写入大量报表数据,如果数据库连接数配额配小了,会频繁报 "can not get connection"。这个问题的解决方式是在datasources.xml里把最大连接数调大,并同时调大 MySQL 侧的最大连接数限制。注意不是只在一边调,两边的值都要匹配。

5.5 报表数据保留时间不够,先别急着上 HDFS

CAT 默认的历史报表数据是存在本地文件,并依赖 HDFS 做长期归档。如果你的诉求只是保留最近一周的数据用于日常排查,那可以先不上 HDFS。但我踩过的坑是,默认的本地归档清理逻辑如果不摸清,磁盘会被报表文件塞满。建议在服务端配置里明确报表文件的保留策略,比如定期清理超过 3 天的本地文件,磁盘告警提前配置好,别等服务器写满再处理。

5.6 日志里一大堆 JSON 解析错误,版本不匹配

客户端 3.0 以上的版本和服务端 3.0 以下的版本混用,会有序列化协议上的问题。最常见的表现是客户端上报数据时报 JSON 解析异常,但网络和配置看起来完全正常。排查到这一步一定要去看客户端实际使用的 cat-client 版本,和生产服务端版本保持一致。版本管理这一环建议纳入发布流水线,避免上线时节点间的 SDK 版本不一致。

把上面这些坑汇总成一张表,方便收藏:

问题具象表现我的处理方式
数据目录权限不足控制台空白,日志大量目录报错chown 给到应用启动用户
IP 配置错误客户端连不上服务端改用内网域名配置
时间不同步报表时间错乱,告警误报全节点 NTP 校时
MySQL 连接池不足服务端频繁报数据库连接异常同步调大客户端与服务端连接池
本地文件无限增长磁盘爆满配置清理策略,提前告警
SDK 版本不一致上报数据解析异常统一 cat-client 版本

6. 一次真实排障链路:CAT 是怎么帮我省下三个小时的

6.1 场景描述:模拟项目 X 的支付回调接口变慢

某天下午,支付回调接口的耗时毛刺变得很明显。从全局看,P99 从 300 毫秒升到 1.5 秒,但数据库、缓存、消息队列都没有明显压力,链路也不知道卡在哪。这种场景在没有监控的时候是最痛苦的,因为系统所有组件都"看似正常",问题藏在调用栈深处。

6.2 定位过程:从 Transaction 树形下钻

打开 CAT 控制台,在 Transaction 报表里找到支付回调接口,点进去可以看到这通调用的完整链路树。首先看到总耗时 1.5 秒,子节点下钻下去,约 800 毫秒消耗在一个内部服务 A 的 HTTP 调用上。点进 A 服务,继续下钻,又看到 750 毫秒消耗在某个连接池获取连接上。继续点进去,发现是这个连接池配置的最大等待时间不合理,某个线程把连接占用的时间又特别长,后面的请求全在排队。

整个定位过程大概 20 分钟。如果没有 CAT,我得先在负载均衡层、网络层、每个服务日志里来回翻,保守估计三个小时起。这也验证了选型时的判断:复杂的业务链路,不能只靠"全局性能指标",还要有能逐层下钻的树形链路,而 CAT 的 Transaction 就是为这个场景设计的。

6.3 事后复盘的三个改善动作

这次排障给我们团队带来的改善不止是修了一个参数。第一,我们给所有外部 HTTP 调用统一加了 CAT 埋点,让每个调用的等待时间、耗时、成功率都能在报表里看到;第二,把连接池获取连接的耗时也做了监控,超过阈值立刻告警;第三,把这次排查经验沉淀为团队文档,遇到类似问题先看 Transaction 树形结构的"耗时占比分布",优先钻取占比最高的子节点。

7. 一些私人经验谈

用了一段时间之后,最大的体会是:CAT 的安装只是一阵子的事,埋点习惯才是一辈子的事。服务端搭起来之后,真正持续维护的其实是"哪些 URL 值得打点、哪些异常必须告警、哪些指标需要长期跟踪"这类规则。不要想着一次把全链路埋完,先挑核心链路,比如登录、下单、支付、回调,跑通之后再逐步铺开。

最后分享一个小技巧:把统一埋点封装进自己的 MVC 拦截器或者 Spring AOP 切面里,所有接口自动生成 Transaction,埋点代码不用散落在业务逻辑里。这样新同事接手时,不需要理解 CAT API,照样能获得完整链路数据。等到哪天接口慢了,打开控制台,看到一个清晰的耗时树,你会感谢当初认真接 CT 的自己。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进