项目背景
CAT(Central Application Tracking),是基于 Java 开发的分布式实时监控系统。CAT在基础存储、高性能通信、大规模在线访问、服务治理、实时监控、容器化及集群智能调度等领域提供业界领先的、统一的解决方案。CAT 目前在美团的产品定位是应用层的统一监控组件,基本接入了美团所有核心应用,在中间件(RPC、数据库、缓存、MQ 等)框架中得到广泛应用,为各业务线提供系统的性能指标、健康状况、实时告警等。
项目地址:https://github.com/dianping/cat
优势
- 实时处理:信息的价值会随时间锐减,尤其是事故处理过程中
- 全量数据:全量采集指标数据,便于深度分析故障案例
- 高可用:故障的还原与问题定位,需要高可用监控来支撑
- 故障容忍:故障不影响业务正常运转、对业务透明
- 高吞吐:海量监控数据的收集,需要高吞吐能力做保证
- 可扩展:支持分布式、跨 IDC 部署,横向扩展的监控系统
监控模型
CAT主要支持以下四种监控模型:
-
Transaction 适合记录跨越系统边界的程序访问行为,比如远程调用,数据库调用,也适合执行时间较长的业务逻辑监控,Transaction用来记录一段代码的执行时间和次数
-
Event 用来记录一件事发生的次数,比如记录系统异常,它和transaction相比缺少了时间的统计,开销比transaction要小
-
Heartbeat 表示程序内定期产生的统计信息, 如CPU利用率, 内存利用率, 连接池状态, 系统负载等
-
Metric 用于记录业务指标、指标可能包含对一个指标记录次数、记录平均值、记录总和,业务指标最低统计粒度为1分钟
消息树
CAT监控系统将每次URL、Service的请求内部执行情况都封装为一个完整的消息树、消息树可能包括Transaction、Event、Heartbeat、Metric等信息。
完整的消息树
功能模块
- cat-client: 客户端,上报监控数据
- cat-consumer: 服务端,收集监控数据进行统计分析,构建丰富的统计报表
- cat-alarm: 实时告警,提供报表指标的监控告警
- cat-hadoop: 数据存储,logview 存储至 Hdfs
- cat-home: 管理端,报表展示、配置管理等
报表介绍
Transaction报表
监控一段代码运行情况:运行次数、QPS、错误次数、失败率、响应时间统计(平均影响时间、Tp分位值)等等
监控一段核心代码执行情况,通过 Transaction进行埋点。
public void flowService() {
service1();
Transaction transaction = Cat.newTransaction("flowService", "Service3");
try {
service3();
transaction.setStatus(Transaction.SUCCESS);
} catch (Exception e) {
transaction.setStatus(e); // catch 到异常,设置状态,代表此请求失败
Cat.logError(e); // 将异常上报到cat上
// 也可以选择向上抛出: throw e;
} finally {
transaction.complete();
}
}
Event报表
监控一段代码运行次数:例如记录程序中一个事件记录了多少次,错误了多少次。Event报表的整体结构与Transaction报表几乎一样,只缺少响应时间的统计。
想记录在某个方法中一个条件分支中分支1中的函数调用了多少次,分支2中的函数调用了多少次,又失败了多少次
public void testEvent() {
for (int i = 0; i < 100; i++) {
Transaction t = Cat.newTransaction("Trans", "test");
for (int j = 0; j < 6000; j++) {
if (j % 3 == 0) {
func1();
Cat.logEvent("Func", "Func1");
} else {
boolean result = func2();
Event e = Cat.newEvent("Func", "Func2");
if (result) {
e.setSuccessStatus();
} else {
e.setStatus("False");
}
e.complete();
}
}
t.setStatus(Transaction.SUCCESS);
t.complete();
}
}
private void func1() {
}
private boolean func2() {
Random random = new Random();
int res = random.nextInt(100);
if (res % 2 == 0)
return false;
return true;
}
Problem报表
Problem记录整个项目在运行过程中出现的问题,包括一些异常、错误、访问较长的行为。Problem报表是由logview存在的特征整合而成,方便用户定位问题。 来源:
- 业务代码显示调用Cat.logError(e) API进行埋点,具体埋点说明可查看埋点文档。
- 与LOG框架集成,会捕获log日志中有异常堆栈的exception日志。
- long-url,表示Transaction打点URL的慢请求
- long-sql,表示Transaction打点SQL的慢请求
- long-service,表示Transaction打点Service或者PigeonService的慢请求
- long-call,表示Transaction打点Call或者PigeonCall的慢请求
- long-cache,表示Transaction打点Cache.开头的慢请求
自定义阈值 对于较长耗时,也认为是problem错误,cat可以选择不同的耗时阈值。例如:接入cat-filter后,此处可以选择long-url不同的阈值,筛选不同的时长URL
Heartbeat报表
Heartbeat报表是CAT客户端,以一分钟为周期,定期向服务端汇报当前运行时候的一些状态
实践如上图通过自定义收集器实现线程池监控
@Component
public class CatFlowThreadCollector extends AbstractCollector {
/**
* 处理大数据推荐普通数据线程池
* */
@Autowired
@Qualifier("threadPoolExecutor")
private ThreadPoolExecutor threadPoolExecutor;
@Override
public String getId() {
return "flowExecutor_pool_monitor";
}
@PostConstruct
public void init(){
StatusExtensionRegister instance = StatusExtensionRegister.getInstance();
instance.register(this);
}
@Override
public Map<String, String> getProperties() {
Map<String,String> map = new LinkedHashMap<>();
//主线程池指标
map.put("flowExecutor.core-pool-size", String.valueOf(threadPoolExecutor.getCorePoolSize()));
map.put("flowExecutor.max-pool-size", String.valueOf(threadPoolExecutor.getMaximumPoolSize()));
map.put("flowExecutor.current-pool-size", String.valueOf(threadPoolExecutor.getPoolSize()));
map.put("flowExecutor.active-count", String.valueOf(threadPoolExecutor.getActiveCount()));
map.put("flowExecutor.queue-size", String.valueOf(threadPoolExecutor.getQueue().size()));
map.put("flowExecutor.completed-task-count", String.valueOf(threadPoolExecutor.getCompletedTaskCount()));
//线程池曾经创建过的最大线程数量
map.put("flowExecutor.largest-pool-size", String.valueOf(threadPoolExecutor.getLargestPoolSize()));
return map;
}
}
Quickstart
Transaction t = Cat.newTransaction("URL", "pageName");
try {
//记录事件
Cat.logEvent("URL.Server", "serverIp", Event.SUCCESS, "ip=${serverIp}");
//记录业务指标的总和或平均值
//如果你在同一秒调用 count 三次(相同的 name),我们会累加他们的值,并且一次性上报给服务端。
//在 duration 的情况下,用平均值来取代累加值。
Cat.logMetricForCount("metric.key");
Cat.logMetricForDuration("metric.key", 5);
yourBusiness();
t.setStatus(Transaction.SUCCESS);
} catch (Exception e) {
t.setStatus(e);
//
Cat.logError(e);
} finally {
t.complete();
}
主流框架集成
spring-boot
@Configuration
public class CatFilterConfigure {
@Bean
public FilterRegistrationBean catFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
CatFilter filter = new CatFilter();
registration.setFilter(filter);
registration.addUrlPatterns("/*");
registration.setName("cat-filter");
registration.setOrder(1);
return registration;
}
}
spring-aop
@Retention(RUNTIME)
@Target(ElementType.METHOD)
public @interface CatAnnotation {
}
@Aspect
public class CatAopService {
@Around(value = "@annotation(CatAnnotation)")
public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature joinPointObject = (MethodSignature) pjp.getSignature();
Method method = joinPointObject.getMethod();
Transaction t = Cat.newTransaction("method", method.getName());
try {
Object res = pjp.proceed();
t.setSuccessStatus();
return res;
} catch (Throwable e) {
t.setStatus(e);
Cat.logError(e);
throw e;
} finally {
t.complete();
}
}
}
我们通过spring-boot方式+aop 方式记录了 url 请求耗时及业务service方法耗时 方便做优化
业务url接口耗时
业务service方法接口耗时
其他如log4j2,logback、mybatis集成方案参考以下地址
https://github.com/dianping/cat/tree/master/integration