一、概述
CAT是由大众点评开源的一款调用链监控系统,基于JAVA开发的。有很多互联网企业在使用,热度非常高。它有一个非常强大和丰富的可视化报表界面,这一点其实对于一款调用链监控系统而来非常的重要。在CAT提供的报表界面中有非常多的功能,几乎能看到你想要的任何维度的报表数据。
特点:聚合报表丰富,中文支持好,国内案例多
1.1 CAT报表介绍
CAT支持如下报表:
| 报表名称 | 报表内容 |
|---|---|
| Transaction报表 | 一段代码的运行时间、次数、比如URL/cache/sql执行次数相应时间 |
| Event报表 | 一段代码运行次数,比如出现一次异常 |
| Problem报表 | 根据Transaction/Event数据分析出系统可能出现的一次,慢程序 |
| Heartbeat报表 | JVM状态信息 |
| Business报表 | 业务指标等,用户可以自己定制 |
二、集成使用
2.1 集成dubbo
添加依赖即可
<dependency>
<groupId>net.dubboclub</groupId>
<artifactId>cat-monitor</artifactId>
<version>0.0.6</version>
</dependency>2.2 集成mybatis
添加依赖
<dependency>
<groupId>com.dianping.cat</groupId>
<artifactId>cat-client</artifactId>
<version>3.0.0</version>
</dependency>集成cat-mybatis插件 该文件来源于cat的官方代码。
import com.alibaba.druid.pool.DruidDataSource;
import com.dianping.cat.Cat;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.Transaction;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 1.Cat-Mybatis plugin: Rewrite on the version of Steven;
* 2.Support DruidDataSource,PooledDataSource(mybatis Self-contained data source);
* @author zhanzehui(west_20@163.com)
*/
@Intercepts({
@Signature(method = "query", type = Executor.class, args = {
MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class }),
@Signature(method = "update", type = Executor.class, args = { MappedStatement.class, Object.class })
})
public class CatMybatisPlugin implements Interceptor {
private static final Pattern PARAMETER_PATTERN = Pattern.compile("\\?");
private static final String MYSQL_DEFAULT_URL = "jdbc:mysql://UUUUUKnown:3306/%s?useUnicode=true";
private Executor target;
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = this.getStatement(invocation);
String methodName = this.getMethodName(mappedStatement);
Transaction t = Cat.newTransaction("SQL", methodName);
String sql = this.getSql(invocation,mappedStatement);
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
Cat.logEvent("SQL.Method", sqlCommandType.name().toLowerCase(), Message.SUCCESS, sql);
String url = this.getSQLDatabaseUrlByStatement(mappedStatement);
Cat.logEvent("SQL.Database", url);
return doFinish(invocation,t);
}
private MappedStatement getStatement(Invocation invocation) {
return (MappedStatement)invocation.getArgs()[0];
}
private String getMethodName(MappedStatement mappedStatement) {
String[] strArr = mappedStatement.getId().split("\\.");
String methodName = strArr[strArr.length - 2] + "." + strArr[strArr.length - 1];
return methodName;
}
private String getSql(Invocation invocation, MappedStatement mappedStatement) {
Object parameter = null;
if(invocation.getArgs().length > 1){
parameter = invocation.getArgs()[1];
}
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
Configuration configuration = mappedStatement.getConfiguration();
String sql = sqlResolve(configuration, boundSql);
return sql;
}
private Object doFinish(Invocation invocation,Transaction t) throws InvocationTargetException, IllegalAccessException {
Object returnObj = null;
try {
returnObj = invocation.proceed();
t.setStatus(Transaction.SUCCESS);
} catch (Exception e) {
Cat.logError(e);
throw e;
} finally {
t.complete();
}
return returnObj;
}
private String getSQLDatabaseUrlByStatement(MappedStatement mappedStatement) {
String url = null;
DataSource dataSource = null;
try {
Configuration configuration = mappedStatement.getConfiguration();
Environment environment = configuration.getEnvironment();
dataSource = environment.getDataSource();
url = switchDataSource(dataSource);
return url;
} catch (NoSuchFieldException|IllegalAccessException|NullPointerException e) {
Cat.logError(e);
}
Cat.logError(new Exception("UnSupport type of DataSource : "+dataSource.getClass().toString()));
return MYSQL_DEFAULT_URL;
}
private String switchDataSource(DataSource dataSource) throws NoSuchFieldException, IllegalAccessException {
String url = null;
if(dataSource instanceof DruidDataSource) {
url = ((DruidDataSource) dataSource).getUrl();
}else if(dataSource instanceof PooledDataSource) {
Field dataSource1 = dataSource.getClass().getDeclaredField("dataSource");
dataSource1.setAccessible(true);
UnpooledDataSource dataSource2 = (UnpooledDataSource)dataSource1.get(dataSource);
url =dataSource2.getUrl();
}else {
//other dataSource expand
}
return url;
}
public String sqlResolve(Configuration configuration, BoundSql boundSql) {
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (parameterMappings.size() > 0 && parameterObject != null) {
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(resolveParameterValue(parameterObject)));
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
Matcher matcher = PARAMETER_PATTERN.matcher(sql);
StringBuffer sqlBuffer = new StringBuffer();
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
Object obj = null;
if (metaObject.hasGetter(propertyName)) {
obj = metaObject.getValue(propertyName);
} else if (boundSql.hasAdditionalParameter(propertyName)) {
obj = boundSql.getAdditionalParameter(propertyName);
}
if (matcher.find()) {
matcher.appendReplacement(sqlBuffer, Matcher.quoteReplacement(resolveParameterValue(obj)));
}
}
matcher.appendTail(sqlBuffer);
sql = sqlBuffer.toString();
}
}
return sql;
}
private String resolveParameterValue(Object obj) {
String value = null;
if (obj instanceof String) {
value = "'" + obj.toString() + "'";
} else if (obj instanceof Date) {
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
value = "'" + formatter.format((Date) obj) + "'";
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "";
}
}
return value;
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
this.target = (Executor) target;
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
}
}2.3 集成日志框架
CAT集成日志框架以logback日志框架为例:
添加依赖
<dependency>
<groupId>com.dianping.cat</groupId>
<artifactId>cat-client</artifactId>
<version>3.0.0</version>
</dependency>创建CatLogbackAppender类,
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.ThrowableProxy;
import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.LogbackException;
import com.dianping.cat.Cat;
import java.io.PrintWriter;
import java.io.StringWriter;
public class CatLogbackAppender extends AppenderBase<ILoggingEvent> {
@Override
protected void append(ILoggingEvent event) {
try {
boolean isTraceMode = Cat.getManager().isTraceMode();
Level level = event.getLevel();
if (level.isGreaterOrEqual(Level.ERROR)) {
logError(event);
} else if (isTraceMode) {
logTrace(event);
}
} catch (Exception ex) {
throw new LogbackException(event.getFormattedMessage(), ex);
}
}
private void logError(ILoggingEvent event) {
ThrowableProxy info = (ThrowableProxy) event.getThrowableProxy();
if (info != null) {
Throwable exception = info.getThrowable();
Object message = event.getFormattedMessage();
if (message != null) {
Cat.logError(String.valueOf(message), exception);
} else {
Cat.logError(exception);
}
}
}
private void logTrace(ILoggingEvent event) {
String type = "Logback";
String name = event.getLevel().toString();
Object message = event.getFormattedMessage();
String data;
if (message instanceof Throwable) {
data = buildExceptionStack((Throwable) message);
} else {
data = event.getFormattedMessage().toString();
}
ThrowableProxy info = (ThrowableProxy) event.getThrowableProxy();
if (info != null) {
data = data + '\n' + buildExceptionStack(info.getThrowable());
}
Cat.logTrace(type, name, "0", data);
}
private String buildExceptionStack(Throwable exception) {
if (exception != null) {
StringWriter writer = new StringWriter(2048);
exception.printStackTrace(new PrintWriter(writer));
return writer.toString();
} else {
return "";
}
}
}修改logback-spring.xml配置文件:
<appender name="CatAppender" class="com.itcast.logbackcat.cat.CatLogbackAppender"></appender>
<root level="info">
<appender-ref ref="consoleLog"/>
<appender-ref ref="fileInfoLog"/>
<appender-ref ref="fileErrorLog"/>
<appender-ref ref="CatAppender" />
</root>
</configuration>2.4 集成springboot
添加如下配置类到config包中
import com.dianping.cat.servlet.CatFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@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;
}
}2.5 Spring AOP拓展
使用Spring AOP技术可以简化我们的埋点操作,通过添加统一注解的方式,使得指定方法被能被CAT监控起来。
添加依赖
<dependency>
<groupId>com.dianping.cat</groupId>
<artifactId>cat-client</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>创建AOP处理
package com.itcast.springaopcat.aop;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target(ElementType.METHOD)
public @interface CatAnnotation {
}package com.itcast.springaopcat.aop;
import java.lang.reflect.Method;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import com.dianping.cat.Cat;
import com.dianping.cat.message.Transaction;
import org.springframework.stereotype.Component;
@Component
@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();
}
}
}2.6 集成Spring MVC
Spring MVC的集成方式,官方提供的是使用AOP来进行集成,源码如下:
AOP接口
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CatTransaction {
String type() default "Handler";//"URL MVC Service SQL" is reserved for Cat Transaction Type
String name() default "";
}
AOP处理代码:
@Around("@annotation(catTransaction)")
public Object catTransactionProcess(ProceedingJoinPoint pjp, CatTransaction catTransaction) throws Throwable {
String transName = pjp.getSignature().getDeclaringType().getSimpleName() + "." + pjp.getSignature().getName();
if(StringUtils.isNotBlank(catTransaction.name())){
transName = catTransaction.name();
}
Transaction t = Cat.newTransaction(catTransaction.type(), transName);
try {
Object result = pjp.proceed();
t.setStatus(Transaction.SUCCESS);
return result;
} catch (Throwable e) {
t.setStatus(e);
throw e;
}finally{
t.complete();
}
}
因这部分与Spring AOP处理方式基本你一样,如需集成请详见Spring AOP。
三、API介绍
3.1 Transaction
Transaction 适合记录跨越系统边界的程序访问行为,比如远程调用,数据库调用,也适合执行时间较长的业务逻辑监控,Transaction用来记录一段代码的执行时间和次数。
手动编写一个本地方法,来测试Transaction的用法,创建TransactionController用于测试。
import com.dianping.cat.Cat;
import com.dianping.cat.message.Transaction;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/transaction")
public class TransactionController {
@RequestMapping("/test")
public String test(){
//开启第一个Transaction,类别为URL,名称为test
Transaction t = Cat.newTransaction("URL", "test");
try {
dubbo();
t.setStatus(Transaction.SUCCESS);
} catch (Exception e) {
t.setStatus(e);
Cat.logError(e);
} finally {
t.complete();
}
return "test";
}
private String dubbo(){
//开启第二个Transaction,类别为DUBBO,名称为dubbo
Transaction t = Cat.newTransaction("DUBBO", "dubbo");
try {
t.setStatus(Transaction.SUCCESS);
} catch (Exception e) {
t.setStatus(e);
Cat.logError(e);
} finally {
t.complete();
}
return "test";
}
}上面的代码中,开启了两个Transaction,其中第一个Transaction为Controller接收到的接口调用,第二个编写的本地方法dubbo用来模拟远程调用。在方法内部,开启第二个Transaction。
扩展API
CAT提供了一系列 API 来对 Transaction 进行修改。
| 方法名 | 作用 |
|---|---|
| addData | 添加额外的数据显示 |
| setStatus | 设置状态,成功可以设置SUCCESS,失败可以设置异常 |
| setDurationInMillis | 设置执行耗时(毫秒) |
| setTimestamp | 设置执行时间 |
| complete | 结束Transaction |
编写如下代码进行测试:
@RequestMapping("/api")
public String api(){
Transaction t = Cat.newTransaction("URL", "pageName");
try {
//设置执行时间1秒
t.setDurationInMillis(1000);
t.setTimestamp(System.currentTimeMillis());
//添加额外数据
t.addData("content");
t.setStatus(Transaction.SUCCESS);
} catch (Exception e) {
t.setStatus(e);
Cat.logError(e);
} finally {
t.complete();
}
return "api";
}3.2 Event
Event 用来记录一件事发生的次数,比如记录系统异常,它和transaction相比缺少了时间的统计,开销比transaction要小。
Cat.logEvent
记录一个事件。
Cat.logEvent("URL.Server", "serverIp", Event.SUCCESS, "ip=${serverIp}");Cat.logError
记录一个带有错误堆栈信息的 Error。
Error 是一种特殊的事件,它的 type 取决于传入的 Throwable e.
- 如果
e是一个Error,type会被设置为Error。 - 如果
e是一个RuntimeException,type会被设置为RuntimeException。 - 其他情况下,
type会被设置为Exception。
同时错误堆栈信息会被收集并写入 data 属性中。
try {
int i = 1 / 0;
} catch (Throwable e) {
Cat.logError(e);
}你可以向错误堆栈顶部添加你自己的错误消息,如下代码所示:
Cat.logError("error(X) := exception(X)", e);编写案例测试上述API:
import com.dianping.cat.Cat;
import com.dianping.cat.message.Event;
import com.dianping.cat.message.Transaction;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/event")
public class EventController {
@RequestMapping("/logEvent")
public String logEvent(){
Cat.logEvent("URL.Server", "serverIp",
Event.SUCCESS, "ip=127.0.0.1");
return "test";
}
@RequestMapping("/logError")
public String logError(){
try {
int i = 1 / 0;
} catch (Throwable e) {
Cat.logError("error(X) := exception(X)", e);
}
return "test";
}
}3.3 Metric
Metric 用于记录业务指标、指标可能包含对一个指标记录次数、记录平均值、记录总和,业务指标最低统计粒度为1分钟。
# Counter
<NolebasePageProperties />
Cat.logMetricForCount("metric.key");
Cat.logMetricForCount("metric.key", 3);
# Duration
Cat.logMetricForDuration("metric.key", 5);每秒会聚合 metric。
举例来说,如果在同一秒调用 count 三次(相同的 name),累加他们的值,并且一次性上报给服务端。
在 duration 的情况下,用平均值来取代累加值。
编写案例测试上述API:
import com.dianping.cat.Cat;
import com.dianping.cat.message.Event;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/metric")
public class MetricController {
@RequestMapping("/count")
public String count(){
Cat.logMetricForCount("count");
return "test";
}
@RequestMapping("/duration")
public String duration(){
Cat.logMetricForDuration("duration", 1000);
return "test";
}
}四、CAT监控界面
4.1 DashBoard
DashBoard仪表盘显示了每分钟出现错误的系统及其错误的次数和时间。
- 点击右上角的时间按钮可以切换不同的展示时间,-7d代表7天前,-1h代表1小时前,now定位到当前时间
- 上方的时间轴按照分钟进行排布,点击之后可以看到该时间到结束的异常情况
- 下方标识了出错的系统和出错的时间、次数,点击系统名称可以跳转到Problem报表
4.2 Transaction
Transaction报表用来监控一段代码运行情况:运行次数、QPS、错误次数、失败率、响应时间统计(平均影响时间、Tp分位值)等等。
应用启动后默认会打点的部分:
| 打点 | 来源组件 | 描述 |
|---|---|---|
| System | cat-client | 上报监控数据的打点信息 |
| URL | 需要接入cat-filter | URL访问的打点信息 |
小时报表
Type统计界面展示了一个Transaction的第一层分类的视图,可以知道这段时间里面一个分类运行的次数,平均响应时间,延迟,以及分位线。
从上而下分析报表:
报表的时间跨度 CAT默认是以一小时为统计时间跨度,点击[切到历史模式],更改查看报表的时间跨度:默认是小时模式;切换为历史模式后,右侧快速导航,变为month(月报表)、week(周报表)、day(天报表),可以点击进行查看,注意报表的时间跨度会有所不同。
时间选择 通过右上角时间导航栏选择时间:点击[+1h]/[-1h]切换时间为下一小时/上一小时;点击[+1d]/[-1d]切换时间为后一天的同一小时/前一天的同一小时;点击右上角[+7d]/[-7d]切换时间为后一周的同一小时/前一周的同一小时;点击[now]回到当前小时。
项目选择 输入项目名,查看项目数据;如果需要切换其他项目数据,输入项目名,回车即可。
机器分组 CAT可以将若干个机器,作为一个分组进行数据统计。默认会有一个All分组,代表所有机器的统计数据,即集群统计数据。
所有Type汇总表格 第一层分类(Type),点击查看第二级分类(称为name)数据
Transaction的埋点的Type和Name由业务自己定义,当打点了Cat.newTransaction(type, name)时,第一层分类是type,第二级分类是name。
第二级分类数据叫是统计相同type下的所有name数据,数据均与第一级(type)一样的展示风格
单个Type指标图表 点击show,查看Type所有name分钟级统计,
指标说明 显示的是小时粒度第一级分类(type)的次数、错误数、失败率等数据。
样本logview L代表logview,为一个样例的调用链路。
分位线说明 小时粒度的时间第一级分类(type)相关统计
- 95line表示95%的请求的响应时间比参考值要小,999line表示99.9%的响应时间比参考值要小,95line以及99line,也称之为tp95、tp99。
历史报表
Transaction历史报表支持每天、每周、每月的数据统计以及趋势图,点击导航栏的切换历史模式进行查询。Transaction历史报表以响应时间、访问量、错误量三个维度进行展示,以天报表为例:选取一个type,点击show,即可查看天报表。
4.3 Event
Event报表监控一段代码运行次数:例如记录程序中一个事件记录了多少次,错误了多少次。Event报表的整体结构与Transaction报表几乎一样,只缺少响应时间的统计。
第一级分类(Type)统计界面
Type统计界面展示了一个Event的第一层分类的视图,Event相对于Transaction少了运行时间统计。可以知道这段时间里面一个分类运行的次数,失败次数,失败率,采样logView,QPS。
第二级分类(Name)统计界面
第二级分类在Type统计界面中点击具体的Type进入,展示的是相同type下所有的name数据,可以理解为某type下更细化的分类。
4.4 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.开头的慢请求
所有错误汇总报表 第一层分类(Type),代表错误类型,比如error、long-url等;第二级分类(称为Status),对应具体的错误,比如一个异常类名等。
错误数分布 点击type和status的show,分别展示type和status的分钟级错误数分布:
4.5 HeartBeat
Heartbeat报表是CAT客户端,以一分钟为周期,定期向服务端汇报当前运行时候的一些状态。
JVM相关指标
以下所有的指标统计都是1分钟内的值,cat最低统计粒度是一分钟。
| JVM GC 相关指标 | 描述 |
|---|---|
| NewGc Count / PS Scavenge Count | 新生代GC次数 |
| NewGc Time / PS Scavenge Time | 新生代GC耗时 |
| OldGc Count | 老年代GC次数 |
| PS MarkSweepTime | 老年代GC耗时 |
| Heap Usage | Java虚拟机堆的使用情况 |
| None Heap Usage | Java虚拟机Perm的使用情况 |
| JVM Thread 相关指标 | 描述 |
|---|---|
| Active Thread | 系统当前活动线程 |
| Daemon Thread | 系统后台线程 |
| Total Started Thread | 系统总共开启线程 |
| Started Thread | 系统每分钟新启动的线程 |
| CAT Started Thread | 系统中CAT客户端启动线程 |
可以参考java.lang.management.ThreadInfo的定义
系统指标
| System 相关指标 | 描述 |
|---|---|
| System Load Average | 系统Load详细信息 |
| Memory Free | 系统memoryFree情况 |
| FreePhysicalMemory | 物理内存剩余空间 |
| / Free | /根的使用情况 |
| /data Free | /data盘的使用情况 |
4.6 Business
Business报表对应着业务指标,比如订单指标。与Transaction、Event、Problem不同,Business更偏向于宏观上的指标,另外三者偏向于微观代码的执行情况。
场景示例:
1. 我想监控订单数量。
2. 我想监控订单耗时。基线
基线是对业务指标的预测值。
基线生成算法:
最近一个月的4个每周几的数据加权求和平均计算得出,秉着更加信任新数据的原则,cat会基于历史数据做异常点的修正,会把一些明显高于以及低于平均值的点剔除。
举例:今天是2018-10-25(周四),今天整天基线数据的算法是最近四个周四(2018-10-18,2018-10-11,2018-10-04,2018-09-27)的每个分钟数据的加权求和或平均,权重值依次为1,2,3,4。如:当前时间为19:56分设为value,前四周对应的19:56分数据(由远及近)分别为A,B,C,D,则value = (A+2B+3C+4D) / 10。
对于刚上线的应用,第一天没有基线,第二天的基线基线是前一天的数据,以此类推。
如何开启基线:
只有配置了基线告警的指标,才会自动计算基线。如需基线功能,请配置基线告警。
注意事项
打点尽量用纯英文,不要带一些特殊符号,例如 空格( )、分号(:)、竖线(|)、斜线(/)、逗号(,)、与号(&)、星号(*)、左右尖括号(<>)、以及一些奇奇怪怪的字符如果有分隔需求,建议用下划线(_)、中划线(-)、英文点号(.)等由于数据库不区分大小写,请尽量统一大小写,并且不要对大小写进行改动有可能出现小数:趋势图每个点都代表一分钟的值。假设监控区间是10分钟,且10分钟内总共上报5次,趋势图中该点的值为5%10=0.5
4.7 State
State报表显示了与CAT相关的信息。
五、告警配置
CAT提供给我们完善的告警功能。合理、灵活的监控规则可以帮助更快、更精确的发现业务线上故障。
5.1 告警服务器配置
只有配置为告警服务器的机器,才会执行告警逻辑;只有配置为发送服务器的机器,才会发送告警。
进入功能 全局系统配置-服务端配置,修改服务器类型,对告警服务器增加<property name="alarm-machine" value="true"/>配置、以及<property name="send-machine" value="true"/>配置。
5.2 告警策略
告警策略:配置某种告警类型、某个项目、某个错误级别,对应的告警发送渠道,以及暂停时间。
举例:下述配置示例,说明对于Transaction告警,当告警项目名为demo_project:
- 当告警级别为error时,发送渠道为邮件、短信、微信,连续告警之间的间隔为5分钟
- 当告警级别为warning时,发送渠道为邮件、微信,连续告警之间的间隔为10分钟
配置示例
<alert-policy>
<type id="Transaction">
<group id="default">
<level id="error" send="mail,weixin" suspendMinute="5"/>
<level id="warning" send="mail,weixin" suspendMinute="5"/>
</group>
<group id="demo-project">
<level id="error" send="mail,weixin,sms" suspendMinute="5"/>
<level id="warning" send="mail,weixin" suspendMinute="10"/>
</group>
</type>
</alert-policy>配置说明:
- type:告警的类型,可选:Transaction、Event、Business、Heartbeat
- group id属性:group可以为default,代表默认,即所有项目;也可以为项目名,代表某个项目的策略,此时default策略不会生效
- level id属性:错误级别,分为warning代表警告、error代表错误
- level send属性:告警渠道,分为mail-邮箱、weixin-微信、sms-短信
- level suspendMinute属性:连续告警的暂停时间
5.3 告警接收人
告警接收人,为告警所属项目的联系人:
- 项目组邮件:项目负责人邮件,或项目组产品线邮件,多个邮箱由英文逗号分割,不要留有空格;作为发送告警邮件、微信的依据
- 项目组号码:项目负责人手机号;多个号码由英文逗号分隔,不要留有空格;作为发送告警短信的依据
5.4 告警服务端
告警发送中心的配置。(什么是告警发送中心:提供发送短信、邮件、微信功能,且提供Http API的服务)
CAT在生成告警后,调用告警发送中心的Http接口发送告警。CAT自身并不集成告警发送中心,请自己搭建告警发送中心。
配置示例
<sender-config>
<sender id="mail" url="http://test/" type="post" successCode="200" batchSend="true">
<par id="type=1500"/>
<par id="key=title,body"/>
<par id="re=test@test.com"/>
<par id="to=${receiver}"/>
<par id="value=${title},${content}"/>
</sender>
<sender id="weixin" url="http://test/" type="post" successCode="success" batchSend="true">
<par id="domain=${domain}"/>
<par id="email=${receiver}"/>
<par id="title=${title}"/>
<par id="content=${content}"/>
<par id="type=${type}"/>
</sender>
<sender id="sms" url="http://test/" type="post" successCode="200" batchSend="false">
<par id="jsonm={type:808,mobile:'${receiver}',pair:{body='${content}'}}"/>
</sender>
</sender-config>
配置说明:
- sender id属性:告警的类型,可选:mail、sms、weixin
- sender url属性:告警中心的URL
- sender batchSend属性:是否支持批量发送告警信息
- par:告警中心所需的Http参数。${argument}代表构建告警对象时,附带的动态参数;此处需要根据告警发送中心的需求,将动态参数加入到代码AlertEntity中的m_paras
5.5 告警规则
目前CAT的监控规则有五个要素
告警时间段。同一项业务指标在每天不同的时段可能有不同的趋势。设定该项,可让CAT在每天不同的时间段执行不同的监控规则。注意:告警时间段,不是监控数据的时间段,只是告警从这一刻开始进行检查数据
规则组合。在一个时间段中,可能指标触发了多个监控规则中的一个规则就要发出警报,也有可能指标要同时触发了多个监控规则才需要发出警报。
监控规则类型。通过以下六种类型对指标进行监控:最大值、最小值、波动上升百分比、波动下降百分比、总和最大值、总和最小值
监控最近分钟数。设定时间后(单位为分钟),当指标在设定的最近的时间长度内连续触发了监控规则,才会发出警报。比如最近分钟数为3,表明连续三分钟的数组都满足条件才告警。如果分钟数为1,表示最近的一分钟满足条件就告警
规则与被监控指标的匹配。监控规则可以按照名称、正则表达式与监控的对象(指标)进行匹配
子条件类型:
有六种类型。子条件的内容为对应的阈值,请注意阈值只能由数字组成,当阈值表达百分比时,不能在最后加上百分号。八种类型如下:
| 类型 | 说明 |
|---|---|
| MaxVal 最大值(当前值) | 当前实际值 最大值,比如检查最近3分钟数据,3分钟数据会有3个value,是表示(>=N)个值都必须同时>=设定值 |
| MinVal 最小值(当前值) | 当前实际值 最小值,比如检查最近3分钟数据,3分钟数据会有3个value,是表示(>=N)个值都必须同时比<=设定值 |
| FluAscPer 波动上升百分比(当前值) | 波动百分比最大值。即当前最后(N)分钟值比监控周期内其它分钟值(M-N个)的增加百分比都>=设定的百分比时触发警报,比如检查最近10分钟数据,触发个数为3;10分钟内数据会算出7个百分比数据,是表示最后3分钟值分别相比前面7分钟值,3组7次比较的上升波动百分比全部>=配置阈值。比如下降50%,阈值填写50。 |
| FluDescPer 波动下降百分比(当前值) | 波动百分比最小值。当前最后(N)分钟值比监控周期内其它(M-N个)分钟值的减少百分比都大于设定的百分比时触发警报,比如检查最近10分钟数据,触发个数为3;10分钟数据会算出7个百分比数据,是表示最后3分钟值分别相比前面7分钟值,3组7次比较的下降波动百分比全部>=配置阈值。比如下降50%,阈值填写50。 |
| SumMaxVal 总和最大值(当前值) | 当前值总和最大值,比如检查最近3分钟数据,表示3分钟内的总和>=设定值就告警。 |
| SumMinVal 总和最小值(当前值) | 当前值总和最小值,比如检查最近3分钟数据,表示3分钟内的总和<=设定值就告警。 |
5.6 Transaction告警
对Transaction的告警,支持的指标有次数、延时、失败率;监控周期:一分钟
配置说明:
- 项目名:要监控的项目名
- type:被监控transaction的type
- name:被监控transaction的name;如果为All,代表全部name
- 监控指标:次数、延时、失败率
- 告警规则:详情见告警规则部分
5.7 Event告警
对Event的个数进行告警;监控周期:一分钟
配置说明:
- 项目名:要监控的项目名
- type:被监控event的type
- name:被监控event的name;如果为All,代表全部name
- 告警规则:详情见告警规则部分
5.8 心跳告警
心跳告警是对服务器当前状态的监控,如监控系统负载、GC数量等信息;监控周期:一分钟
配置说明:
- 项目名:要监控的项目名
- 指标:被监控的心跳指标名称;心跳告警是由两级匹配的:首先匹配项目,然后按照指标匹配
- 告警规则:详情见告警规则部分
5.9 异常告警
对异常的个数进行告警;监控周期:一分钟
配置说明:
- 项目名:要监控的项目名
- 异常名称:被监控异常名称;当设置为“Total”时,是针对当前项目组所有异常总数阈值进行设置;当设置为特定异常名称时,针对当前项目组所有同名的异常阈值进行设定
- warning阈值:到达该阈值,发送warning级别告警;当异常数小于该阈值时,不做任何警报
- error阈值:到达该阈值,发送error级别告警
- 总数大于Warning阈值,小于Error阈值,进行Warning级别告警;大于Error阈值,进行Error级别告警
5.10 告警接口编写
编写controller接口:
package com.itcast.springbootcat;
import com.dianping.cat.Cat;
import com.dianping.cat.message.Event;
import com.dianping.cat.message.Transaction;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
@RestController
public class AlertController {
@RequestMapping(value = "/alert/msg")
public String sendAlert(@RequestParam String to) {
System.out.println("告警了" +to);
return "200";
}
}修改告警服务端的配置,填写接口地址,以邮件为例:
配置示例
<sender id="mail" url="http://localhost:8085/alert/msg" type="post" successCode="200" batchSend="true">
<par id="type=1500"/>
<par id="key=title,body"/>
<par id="re=test@test.com"/>
<par id="to=${receiver}"/>
<par id="value=${title},${content}"/>
</sender>测试结果,输出内容如下:
告警了testUser1@test.com,testUser2@test.com六、安装
6.1 常规安装
从github上下载最新版本的源码。地址
模块介绍
- cat-client: 客户端,上报监控数据
- cat-consumer: 服务端,收集监控数据进行统计分析,构建丰富的统计报表
- cat-alarm: 实时告警,提供报表指标的监控告警
- cat-hadoop: 数据存储,logview 存储至 Hdfs
- cat-home: 管理端,报表展示、配置管理等
CAT服务端的环境要求如下:
- Linux 2.6以及之上(2.6内核才可以支持epoll),线上服务端部署请使用Linux环境,Mac以及Windows环境可以作为开发环境,美团点评内部CentOS 6.5
- Java 6,7,8,服务端推荐使用jdk7的版本,客户端jdk6、7、8都支持
- Maven 3及以上
- MySQL 5.6,5.7,更高版本MySQL都不建议使用,不清楚兼容性
- J2EE容器建议使用tomcat,建议使用推荐版本7.*.或8.0.
- Hadoop环境可选,一般建议规模较小的公司直接使用磁盘模式,可以申请CAT服务端,500GB磁盘或者更大磁盘,这个磁盘挂载在/data/目录上
数据库脚本执行
数据库的脚本文件 script/CatApplication.sql
shmysql -uroot -Dcat < CatApplication.sql说明:
数据库编码使用utf8mb4,否则可能造成中文乱码等问题
tomcat配置
修改中文乱码 tomcat conf 目录下 server.xml
<Connector port="8080" protocol="HTTP/1.1"
URIEncoding="utf-8" connectionTimeout="20000"
redirectPort="8443" /> <!-- 增加 URIEncoding="utf-8" -->应用打包
源码构建
- 在cat的源码目录,执行
mvn clean install -DskipTests - 如果发现cat的war打包不通过,CAT所需要依赖jar都部署在 http://unidal.org/nexus/
- 可以配置这个公有云的仓库地址到本地Maven配置(一般为~/.m2/settings.xml),理论上不需要配置即可,可以参考cat的pom.xml配置:
xml<repositories> <repository> <id>central</id> <name>Maven2 Central Repository</name> <layout>default</layout> <url>http://repo1.maven.org/maven2</url> </repository> <repository> <id>unidal.releases</id> <url>http://unidal.org/nexus/content/repositories/releases/</url> </repository> </repositories>- 在cat的源码目录,执行
官方下载
如果自行打包仍然问题,请使用下面链接进行下载:
官方的cat的master版本,
重命名为cat.war进行部署,注意此war是用jdk8,服务端请使用jdk8版本
安装配置
程序对于/data/目录具体读写权限
要求/data/目录能进行读写操作,如果/data/目录不能写,建议使用linux的软链接链接到一个固定可写的目录。所有的客户端集成程序的机器以及CAT服务端机器都需要进行这个权限初始化。(可以通过公司运维工具统一处理)
此目录会存一些CAT必要的配置文件以及运行时候的数据存储目录。
CAT支持CAT_HOME环境变量,可以通过JVM参数修改默认的路径。
shmkdir /data chmod -R 777 /data/
配置/data/appdatas/cat/client.xml ($CAT_HOME/client.xml)
mkdir -p /data/appdatas/cat
cd /data/appdatas/cat
vi client.xml编写程序运行盘下的/data/appdatas/cat/client.xml,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<config mode="client">
<servers>
<!--下面的IP地址替换为主机的IP地址-->
<server ip="192.168.1.101" port="2280" http-port="8080"/>
</servers>
</config>配置/data/appdatas/cat/datasources.xml($CAT_HOME/datasources.xml)
vi datasources.xml<?xml version="1.0" encoding="utf-8"?>
<data-sources>
<data-source id="cat">
<maximum-pool-size>3</maximum-pool-size>
<connection-timeout>1s</connection-timeout>
<idle-timeout>10m</idle-timeout>
<statement-cache-size>1000</statement-cache-size>
<properties>
<driver>com.mysql.jdbc.Driver</driver>
<url><![CDATA[jdbc:mysql://127.0.0.1:3306/cat]]></url> <!-- 请替换为真实数据库URL及Port -->
<user>root</user> <!-- 请替换为真实数据库用户名 -->
<password>root</password> <!-- 请替换为真实数据库密码 -->
<connectionProperties><![CDATA[useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&socketTimeout=120000]]></connectionProperties>
</properties>
</data-source>
</data-sources>服务端配置
配置链接:http://{ip:port}/cat/s/config?op=serverConfigUpdate
输入账号密码admin/admin进行登录
以下所有IP地址为127.0.0.1内容,均修改为实际的IP地址!
输入以下内容:
<?xml version="1.0" encoding="utf-8"?>
<server-config>
<server id="default">
<properties>
<property name="local-mode" value="false"/>
<property name="job-machine" value="false"/>
<property name="send-machine" value="false"/>
<property name="alarm-machine" value="false"/>
<property name="hdfs-enabled" value="false"/>
<property name="remote-servers" value="127.0.0.1:8080"/>
</properties>
<storage local-base-dir="/data/appdatas/cat/bucket/" max-hdfs-storage-time="15" local-report-storage-time="2" local-logivew-storage-time="1" har-mode="true" upload-thread="5">
<hdfs id="dump" max-size="128M" server-uri="hdfs://127.0.0.1/" base-dir="/user/cat/dump"/>
<harfs id="dump" max-size="128M" server-uri="har://127.0.0.1/" base-dir="/user/cat/dump"/>
<properties>
<property name="hadoop.security.authentication" value="false"/>
<property name="dfs.namenode.kerberos.principal" value="hadoop/dev80.hadoop@testserver.com"/>
<property name="dfs.cat.kerberos.principal" value="cat@testserver.com"/>
<property name="dfs.cat.keytab.file" value="/data/appdatas/cat/cat.keytab"/>
<property name="java.security.krb5.realm" value="value1"/>
<property name="java.security.krb5.kdc" value="value2"/>
</properties>
</storage>
<consumer>
<long-config default-url-threshold="1000" default-sql-threshold="100" default-service-threshold="50">
<domain name="cat" url-threshold="500" sql-threshold="500"/>
<domain name="OpenPlatformWeb" url-threshold="100" sql-threshold="500"/>
</long-config>
</consumer>
</server>
<server id="127.0.0.1">
<properties>
<property name="job-machine" value="true"/>
<property name="send-machine" value="true"/>
<property name="alarm-machine" value="true"/>
</properties>
</server>
</server-config>配置链接:http://{ip:port}/cat/s/config?op=routerConfigUpdate
<?xml version="1.0" encoding="utf-8"?>
<router-config backup-server="127.0.0.1" backup-server-port="2280">
<default-server id="127.0.0.1" weight="1.0" port="2280" enable="true"/>
<network-policy id="default" title="默认" block="false" server-group="default_group">
</network-policy>
<server-group id="default_group" title="default-group">
<group-server id="127.0.0.1"/>
</server-group>
<domain id="cat">
<group id="default">
<server id="127.0.0.1" port="2280" weight="1.0"/>
</group>
</domain>
</router-config>