简介:CAT实时应用监控平台 v3.1.0 源码包面向IT运维人员、Java开发者及计算机专业学生,用于实时监测应用性能与稳定性,可支撑毕业设计论文撰写与分布式监控案例研究。压缩包共2000个文件,约29.13MB,以1108个Java源文件与481个XML配置为主,辅以JS前端脚本、C/C++底层实现、Python与Go辅助模块,以及Markdown说明、YAML与JSON配置等,覆盖系统架构、模块划分、数据处理与异常处理等核心逻辑。包内附说明文档,介绍部署、环境配置、服务启停与Web界面操作,便于快速上手。已有113人学习下载。读者可借此掌握分布式监控的指标采集、日志聚合、报警通知与数据可视化实现思路,理解性能瓶颈定位与故障预防方法,是学习、定制或集成监控方案的完整实践素材。
1. CAT 实时应用监控平台 v3.1.0:从告警延迟到全链路追踪的落地拆解
CAT 实时应用监控平台 v3.1.0 这个包名,第一次看到的人多半会愣一下——CAT 不是 Linux 里那个打印文件的命令吗?其实这里的 CAT 是大众点评开源的那套实时应用监控系统,全称 Central Application Tracking,跟cat命令没有半毛钱关系。它解决的核心问题很具体:一个分布式系统跑起来之后,某个接口突然变慢、某台机器开始报错、某次调用链路断在了哪里,这些信息如果只靠翻日志,排查一次故障可能要半小时起步。CAT 要做的就是把这些数据实时采集、聚合并可视化,让问题在分钟级甚至秒级暴露出来。
这套东西适合谁?如果你手里有十几台以上的应用服务器,服务之间互相调用,出了故障需要快速定位是哪个环节的问题,那 CAT 值得花时间搭起来。如果只是单机小项目,说实话用不上,日志加个 grep 就够了。v3.1.0 这个版本号意味着它是一个相对成熟的发行包,不是早期试验品,部署方式和配置项都有比较稳定的约定。
2. CAT 的埋点模型与数据流转:消息树是怎么长出来的
2.1 从一次 RPC 调用看 CAT 的数据结构
CAT 最核心的设计是「消息树」。每一次请求进来,CAT 会在当前线程的上下文里创建一个根 Message,这个根节点代表整个请求。请求处理过程中,每经过一个关键节点——比如调用了一次数据库、发了一次 RPC、执行了一段缓存读取——就在根节点下面挂一个子节点。子节点还可以继续挂子节点,最终形成一棵树。
这棵树的结构直接决定了你在 CAT 界面上能看到什么。根节点的类型通常是 Transaction,表示一个完整的业务事务。子节点可以是 Event(普通事件,比如一次方法调用耗时)、Metric(指标,比如某段时间内的调用次数)、Heartbeat(心跳,用于存活检测)、Trace(调用链,用于跨服务追踪)。每种类型在 CAT 的服务端有不同的存储和聚合策略,Transaction 和 Event 会保留详细的调用信息,Metric 只做数值聚合。
为什么这么设计?因为监控数据的量级差异太大了。一个日活百万的系统,每秒可能产生几十万条 Event,如果每条都完整存储,磁盘和查询都扛不住。CAT 的做法是:Transaction 和 Event 按小时分片存储,保留时间可配置;Metric 按分钟聚合,只存统计值。这样既保证了排查问题时能拿到细节,又不会让存储成本失控。
2.2 客户端埋点的最小可用配置
CAT 客户端支持 Java、C、Python、Go 等多种语言,但用得最多的还是 Java。下面是一个 Spring Boot 项目接入 CAT 客户端的最小配置,基于 Maven 构建。
<!-- pom.xml 中添加 CAT 客户端依赖 --> <dependency> <groupId>com.dianping.cat</groupId> <artifactId>cat-client</artifactId> <version>3.1.0</version> </dependency>// CatFilter.java - 在 Web 请求入口处初始化 CAT 上下文 import com.dianping.cat.Cat; import com.dianping.cat.message.Transaction; import javax.servlet.*; import java.io.IOException; public class CatFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 创建根 Transaction,类型为 URL,名称为请求路径 Transaction t = Cat.newTransaction("URL", ((HttpServletRequest) request).getRequestURI()); try { chain.doFilter(request, response); t.setStatus(Transaction.SUCCESS); } catch (Exception e) { // 记录异常信息,CAT 会自动关联到当前 Transaction t.setStatus(e); Cat.logError(e); throw e; } finally { // 必须调用 complete,否则消息不会上报 t.complete(); } } }这段代码做了三件事:创建根事务、执行业务逻辑、完成事务上报。Cat.newTransaction会在当前线程的 ThreadLocal 里创建一个上下文,后续所有Cat.logEvent、Cat.logMetric调用都会挂到这棵树上。t.complete()是关键,不调用的话消息会一直留在内存缓冲区里,直到超时被丢弃。
参数方面,Transaction的类型和名称决定了在 CAT 报表里的分类维度。类型一般用URL、SQL、RPC、Cache这些约定俗成的值,名称用具体的接口路径或方法名。名称不要太细,否则报表维度爆炸;也不要太粗,否则定位不到具体问题。我一般建议 URL 类型用Controller的路径,RPC 类型用服务名.方法名。
2.3 跨服务调用链的拼接逻辑
单机的消息树只能看到本机的调用情况,跨服务追踪需要把多个消息树串起来。CAT 的做法是在 RPC 调用的请求头里带上CAT_ROOT_ID、CAT_PARENT_ID、CAT_CHILD_ID三个标识,下游服务收到请求后,用这些标识把本地的消息树挂到上游的树上。
// 在 RPC 框架的客户端拦截器中注入 CAT 上下文 public class CatRpcClientInterceptor implements ClientInterceptor { @Override public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall( MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) { // 获取当前线程的 CAT 上下文 Context ctx = Cat.getManager().getThreadLocalContext(); String rootId = ctx.getDomain() + "-" + ctx.getRootMessageId(); String parentId = ctx.getMessageId(); // 将上下文信息放入请求头 Metadata metadata = new Metadata(); metadata.put(Metadata.Key.of("CAT_ROOT_ID", Metadata.ASCII_STRING_MARSHALLER), rootId); metadata.put(Metadata.Key.of("CAT_PARENT_ID", Metadata.ASCII_STRING_MARSHALLER), parentId); return next.newCall(method, callOptions.withExtraHeaders(metadata)); } }下游服务收到请求后,从请求头里取出CAT_ROOT_ID和CAT_PARENT_ID,调用Cat.getManager().getThreadLocalContext().setRootMessageId(rootId)和setParentMessageId(parentId),这样新创建的消息树就会自动挂到上游的树上。整个链路的拼接逻辑就是这样一层层传递下去的。
这里有个容易翻车的点:如果 RPC 框架用了线程池做异步调用,ThreadLocal 里的上下文不会自动传递到工作线程。需要在提交任务时手动把上下文取出来,在工作线程里重新设置。这个问题在异步化程度高的系统里特别常见,表现就是调用链断断续续,有些节点能看到,有些节点丢失。
3. 服务端部署与存储选型:单机模式还是集群模式
3.1 单机模式的部署步骤与配置项
CAT 服务端本身是一个 Java Web 应用,部署方式很简单:把 war 包丢进 Tomcat,配好数据库连接就行。但「简单」是相对的,配置项没调对,跑起来也会各种问题。
# 1. 创建 CAT 专用数据库,CAT 需要独立的 schema mysql -u root -p -e "CREATE DATABASE cat_db DEFAULT CHARACTER SET utf8mb4;" # 2. 导入 CAT 的表结构,脚本在 war 包的 META-INF/dal 目录下 mysql -u root -p cat_db < cat-mysql.sql # 3. 配置 CAT 服务端的数据库连接 # 编辑 CAT_HOME/webapps/cat/WEB-INF/classes/META-INF/app.properties # 内容如下: # app.name=cat # cat.database.url=jdbc:mysql://127.0.0.1:3306/cat_db # cat.database.user=cat_user # cat.database.password=your_password # 4. 启动 Tomcat $CAT_HOME/bin/startup.sh数据库表结构里最关键的是message表和transaction表,前者存原始消息,后者存聚合后的报表数据。message表的数据量增长很快,单机模式下建议配置定时清理任务,保留最近 7 天的数据就够了。清理逻辑 CAT 自带了一个CatCleanup定时任务,在web.xml里配置 cron 表达式即可。
单机模式适合日活百万以下的系统,再往上就会遇到性能瓶颈。瓶颈通常出现在两个地方:一是数据库写入,每秒几万条消息的插入会让 MySQL 的 IO 跑满;二是报表计算,CAT 服务端需要定期把原始消息聚合成分钟级、小时级的报表,这个计算过程很吃 CPU。
3.2 集群模式的分片策略与消费端配置
集群模式的核心思路是把数据分片存储,每台 CAT 服务端只负责一部分应用的数据。分片规则配置在app.properties里,格式是应用名:分片编号。
# 集群模式下,每台 CAT 服务端配置自己负责的应用 # 服务端 A 的配置 cat.consumer.apps=order-service:1,payment-service:1,user-service:1 # 服务端 B 的配置 cat.consumer.apps=inventory-service:2,logistics-service:2,notification-service:2客户端上报数据时,会先请求一个路由服务(CAT 服务端自带),路由服务根据应用名返回对应的服务端地址列表。客户端拿到地址后,把消息发到对应的服务端。这个路由信息是缓存在客户端本地的,默认 5 分钟刷新一次。
集群模式下的消费端配置有几个关键参数需要调整。cat.consumer.threads控制消费线程数,默认是 3,在高吞吐场景下建议调到 8 到 16。cat.consumer.batch.size控制每次批量拉取的消息条数,默认 100,可以调到 500 减少网络往返。cat.consumer.timeout是消费超时时间,默认 1000 毫秒,如果消息体比较大可以适当调大。
提示:集群模式下,如果某台服务端挂了,它负责的应用数据会暂时丢失,直到运维手动把分片迁移到其他服务端。CAT 本身没有自动故障转移机制,这一点在生产环境需要提前规划好。
3.3 存储选型:MySQL 还是 HBase
CAT 默认用 MySQL 存储,但在数据量大的场景下,HBase 是更常见的选择。两者的差异主要体现在写入吞吐和查询延迟上。
| 维度 | MySQL | HBase |
|---|---|---|
| 写入吞吐 | 单机约 5 万条/秒 | 单机约 20 万条/秒 |
| 查询延迟 | 毫秒级,依赖索引 | 毫秒级,依赖 RowKey 设计 |
| 运维成本 | 低,熟悉的人多 | 高,需要专门的 HBase 运维 |
| 数据保留 | 受磁盘限制,通常 7 天 | 可扩展到 30 天以上 |
| 适用规模 | 日活百万以下 | 日活千万以上 |
如果选择 HBase,需要在app.properties里把cat.database.type改成hbase,并配置 ZooKeeper 地址。HBase 的 RowKey 设计是 CAT 内置好的,格式是应用名-时间戳-消息ID,这个设计保证了同一个应用的数据在物理上连续存储,范围查询效率高。
我一般建议:如果团队里没有人熟悉 HBase 运维,先用 MySQL 跑起来,等数据量真的扛不住了再迁移。迁移过程不算复杂,CAT 支持双写,可以先把新数据写到 HBase,历史数据留在 MySQL,查询时两边都查,等历史数据过期后自然就切过去了。
4. 避坑与排查:CAT 接入过程中最常见的五个问题
4.1 消息不上报,客户端日志显示连接超时
现象:应用启动后,CAT 客户端日志里反复出现connect timed out或connection refused,CAT 服务端界面上看不到任何数据。
原因:客户端配置的服务端地址不对,或者服务端的 2280 端口没有对外开放。CAT 客户端默认连接服务端的 2280 端口,这个端口在防火墙规则里经常被漏掉。
解决:先确认服务端地址配置正确,在app.properties里检查cat.server.address这一项。然后在客户端机器上用telnet cat-server-ip 2280测试连通性。如果不通,检查服务端的防火墙规则和 Tomcat 的 connector 配置。CAT 服务端的 2280 端口是一个自定义的 TCP 端口,不是 Tomcat 的 HTTP 端口,需要在server.xml里单独配置。
4.2 调用链断裂,下游服务看不到上游的 Transaction
现象:在 CAT 界面上查看某个请求的调用链,只能看到上游服务的节点,下游服务的节点是空的,或者下游服务单独显示为一棵独立的树。
原因:跨服务传递上下文时,CAT_ROOT_ID和CAT_PARENT_ID没有正确传递。常见的情况是 RPC 框架的拦截器没有生效,或者异步调用时 ThreadLocal 上下文丢失。
解决:先检查 RPC 框架的拦截器配置,确认 CAT 的拦截器在调用链的最外层。如果是异步调用,需要在提交任务时手动传递上下文。以 Java 的CompletableFuture为例:
// 在异步调用前捕获当前上下文 Context ctx = Cat.getManager().getThreadLocalContext(); String rootId = ctx.getRootMessageId(); String parentId = ctx.getMessageId(); CompletableFuture.supplyAsync(() -> { // 在工作线程中恢复上下文 Context asyncCtx = Cat.getManager().getThreadLocalContext(); asyncCtx.setRootMessageId(rootId); asyncCtx.setParentMessageId(parentId); // 执行业务逻辑 return doSomething(); });4.3 报表数据延迟,分钟级报表要等十几分钟才出来
现象:CAT 界面上实时数据能看到,但分钟级、小时级的报表要等很久才更新,有时候甚至不更新。
原因:CAT 服务端的报表计算任务被阻塞了。常见的原因是数据库连接池不够用,或者报表计算线程被慢查询卡住。
解决:检查 CAT 服务端的数据库连接池配置,默认是 10 个连接,在高负载下不够用。在app.properties里把cat.database.pool.size调到 30 到 50。同时检查message表的索引是否完整,缺少索引会导致报表查询全表扫描。CAT 自带的索引脚本在META-INF/dal目录下,确认都执行过了。
4.4 客户端 CPU 占用高,GC 频繁
现象:接入 CAT 客户端后,应用的 CPU 使用率明显上升,GC 日志里 Full GC 次数增加。
原因:CAT 客户端默认会在内存里缓冲一定量的消息,缓冲区满了才批量发送。如果消息产生速度太快,缓冲区频繁触发扩容和回收,就会导致 GC 压力。
解决:调整客户端的缓冲区大小和发送频率。在app.properties里把cat.client.buffer.size从默认的 5000 调到 10000,同时把cat.client.send.interval从 1000 毫秒调到 500 毫秒。这样每次发送的消息更多,发送频率更高,缓冲区不会积压太多数据。另外,检查是否有大量的Cat.logEvent调用在循环里执行,这种高频埋点会产生大量小对象,对 GC 很不友好。
4.5 服务端磁盘写满,历史数据无法清理
现象:CAT 服务端运行一段时间后,磁盘使用率飙升到 90% 以上,新的消息无法写入。
原因:CAT 的message表数据没有及时清理,或者 HBase 的 Region 没有做 compaction。
解决:如果是 MySQL,检查CatCleanup定时任务是否在运行。这个任务默认每天凌晨 3 点执行,删除 7 天前的数据。如果任务没跑,手动执行一次清理 SQL:DELETE FROM message WHERE timestamp < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 7 DAY)) * 1000;。如果是 HBase,需要检查 RegionServer 的 compaction 队列,手动触发 major compaction。
注意:清理数据前先确认磁盘剩余空间,如果空间已经为零,删除操作也会失败。需要先手动删除一部分数据文件腾出空间,再执行清理任务。
5. 用 Metric 做业务监控:从技术指标到业务指标的最后一公里
CAT 的 Transaction 和 Event 解决的是技术层面的问题——接口耗时、调用链路、异常堆栈。但真正有价值的监控往往在业务层面:下单量突然掉了、支付成功率下降了、某个地区的用户活跃度异常了。这些指标用 Transaction 也能做,但不够直观,而且查询效率低。CAT 的 Metric 类型就是为这类场景设计的。
Metric 的用法很简单,在业务代码里调用Cat.logMetricForCount或Cat.logMetricForDuration就行。但要用好,关键在于指标的设计和聚合维度的选择。
// 记录订单创建数量,按渠道和地区两个维度聚合 public void createOrder(Order order) { // 业务逻辑... // 按渠道统计订单量 Cat.logMetricForCount("order.create.count", 1, "channel", order.getChannel(), "region", order.getRegion()); // 记录订单金额,用于计算客单价 Cat.logMetricForDuration("order.amount", order.getAmount(), "channel", order.getChannel()); }这段代码里,logMetricForCount记录的是计数型指标,适合统计调用次数、成功失败数这类数据。logMetricForDuration记录的是数值型指标,适合统计金额、耗时、大小这类数据。后面的可变参数是标签,CAT 会按标签组合做聚合,在报表里可以按标签筛选和分组。
标签的设计有几个原则。第一,标签值不要太多,每个标签的取值控制在 20 个以内,否则报表维度爆炸,查询会很慢。第二,标签名要有业务含义,用channel、region、product_type这种一看就懂的词,不要用dim1、dim2这种。第三,不要把所有维度都塞进标签里,只保留真正需要分组查看的维度。比如订单 ID 这种唯一值,绝对不能做标签,否则每个订单一个维度,报表直接废掉。
Metric 的报表在 CAT 界面上叫「业务监控」,可以配置告警规则。比如「order.create.count 在 5 分钟内下降超过 30%」就触发告警。这个告警比技术指标的告警更有业务价值,因为它直接反映了用户行为的变化。
我自己的习惯是:每接入一个新业务,先花半小时和业务方对齐三个核心指标——一个量级指标(比如订单量)、一个质量指标(比如成功率)、一个效率指标(比如处理时长)。这三个指标用 Metric 埋下去,配上告警,基本上业务出问题的时候能第一时间知道。技术指标告诉你「哪里疼」,业务指标告诉你「病得重不重」,两者缺一不可。
最后说一个我踩过的坑:Metric 的标签值如果是用户输入的内容,一定要做归一化处理。比如地区字段,用户可能填「北京」、「北京市」、「Beijing」,如果不做归一化,报表里会出现三个不同的维度,数据就分散了。我一般会在埋点前加一层映射,把用户输入统一转成标准编码。这个细节看起来小,但直接影响报表的可用性。希望帮到你。
本文还有配套的精品资源,点击获取