Cat是基于Java开发的实时应用监控平台,为美团点评提供了全面的实时监控告警服务
• CAT作为服务端项目基础组件,提供了java, c/c++, node, python, go等多语言客户端,已经在美团点评的基础架构中间件框架(MVC框架,RPC框架,数据库框架,缓存框架等,消息队列,配置系统等)深度集成,为美团点评各业务线提供系统丰富的性能指标、健康状况、实时告警等。
• CAT很大的优势是它是一个实时系统,CAT大部分系统是分钟级统计,但是从数据生成到服务端处理结束是秒级别,秒级定义是48分钟40秒,基本上看到48分钟38秒数据,整体报表的统计粒度是分钟级;第二个优势,监控数据是全量统计,客户端预计算;链路数据是采样计算。
• 减少线上问题的发现时间
• 减少问题故障的定位时间
• 辅助应用程序的优化工具
• 实时处理:信息的价值会随时间锐减,尤其是事故处理过程中。
• 全量数据:最开始的设计目标就是全量采集,全量的好处有很多。
• 高可用:所有应用都倒下了,需要监控还站着,并告诉工程师发生了什么,做到故障还原和问题定位。
• 故障容忍:CAT 本身故障不应该影响业务正常运转,CAT 挂了,应用不该受影响,只是监控能力暂时减弱。
• 高吞吐:要想还原真相,需要全方位地监控和度量,必须要有超强的处理吞吐能力。
• 可扩展:支持分布式、跨 IDC 部署,横向扩展的监控系统。
• Transaction 适合记录跨越系统边界的程序访问行为,比如远程调用,数据库调用,也适合执行时间较长的业务逻辑监控,Transaction用来记录一段代码的执行时间和次数。
• Event 用来记录一件事发生的次数,比如记录系统异常,它和transaction相比缺少了时间的统计,开销比transaction要小。
• Heartbeat 表示程序内定期产生的统计信息, 如CPU%, MEM%, 连接池状态, 系统负载等。
• Metric 用于记录业务指标、指标可能包含对一个指标记录次数、记录平均值、记录总和,业务指标最低统计粒度为1分钟。
<dependency>
<groupId>com.dianping.catgroupId>
<artifactId>cat-clientartifactId>
<version>3.0.0version>
dependency>
package com.kye.map.ucenter.controller;
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;
/**
* Description:cat的的核心过滤器
*
* Date: 2018/10/24 15:34
*
*/
@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;
}
}
引入这个以后cat项目就能监控到你访问的url
app.name=ucenter 这个必须有,cat服务端必须通过这个找到相应的项目
client.xml内容如下:
<config mode="client" xmlns:xsi="http://www.w3.org/2001/XMLSchema" xsi:noNamespaceSchemaLocation="config.xsd">
<servers>
<server ip="10.10.242.9" port="2280" http-port="8080" />
servers>
config>
cat项目的日志目录:
)如果项目启动出问题,或者cat监控不到自己的项目,可以看看这里的日志)
package com.kye.map.ucenter.config;
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.commons.dbcp.BasicDataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
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 org.mybatis.spring.transaction.SpringManagedTransaction;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
/**
* 对MyBatis进行拦截,添加Cat监控
* 目前仅支持RoutingDataSource和Druid组合配置的数据源
*
* @author Steven
*/
@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 Log logger = LogFactory.getLog(CatMybatisPlugin.class);
//缓存,提高性能
private static final Map<String, String> sqlURLCache = new ConcurrentHashMap<String, String>(256);
private static final String EMPTY_CONNECTION = "jdbc:mysql://localhost:3306/%s?useUnicode=true";
private Executor target;
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
//得到类名,方法
String[] strArr = mappedStatement.getId().split("\\.");
String methodName = strArr[strArr.length - 2] + "." + strArr[strArr.length - 1];
Transaction t = Cat.newTransaction("SQL", methodName);
//得到sql语句
Object parameter = null;
if(invocation.getArgs().length > 1){
parameter = invocation.getArgs()[1];
}
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
Configuration configuration = mappedStatement.getConfiguration();
String sql = showSql(configuration, boundSql);
//获取SQL类型
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
Cat.logEvent("SQL.Method", sqlCommandType.name().toLowerCase(), Message.SUCCESS, sql);
String s = this.getSQLDatabase();
Cat.logEvent("SQL.Database", s);
Object returnObj = null;
try {
returnObj = invocation.proceed();
t.setStatus(Transaction.SUCCESS);
} catch (Exception e) {
t.setStatus(e);
Cat.logError(e);
} finally {
t.complete();
}
return returnObj;
}
private javax.sql.DataSource getDataSource() {
org.apache.ibatis.transaction.Transaction transaction = this.target.getTransaction();
if (transaction == null) {
logger.error(String.format("Could not find transaction on target [%s]", this.target));
return null;
}
if (transaction instanceof SpringManagedTransaction) {
String fieldName = "dataSource";
Field field = ReflectionUtils.findField(transaction.getClass(), fieldName, javax.sql.DataSource.class);
if (field == null) {
logger.error(String.format("Could not find field [%s] of type [%s] on target [%s]",
fieldName, javax.sql.DataSource.class, this.target));
return null;
}
ReflectionUtils.makeAccessible(field);
javax.sql.DataSource dataSource = (javax.sql.DataSource) ReflectionUtils.getField(field, transaction);
return dataSource;
}
logger.error(String.format("---the transaction is not SpringManagedTransaction:%s", transaction.getClass().toString()));
return null;
}
private String getSqlURL() {
javax.sql.DataSource dataSource = this.getDataSource();
if (dataSource == null) {
return null;
}
if (dataSource instanceof AbstractRoutingDataSource) {
String methodName = "determineTargetDataSource";
Method method = ReflectionUtils.findMethod(AbstractRoutingDataSource.class, methodName);
if (method == null) {
logger.error(String.format("---Could not find method [%s] on target [%s]",
methodName, dataSource));
return null;
}
ReflectionUtils.makeAccessible(method);
javax.sql.DataSource dataSource1 = (javax.sql.DataSource) ReflectionUtils.invokeMethod(method, dataSource);
if (dataSource1 instanceof DruidDataSource) {
DruidDataSource druidDataSource = (DruidDataSource) dataSource1;
return druidDataSource.getUrl();
} else {
logger.error("---only surpport DruidDataSource:" + dataSource1.getClass().toString());
}
} else if(dataSource instanceof BasicDataSource){
return ((BasicDataSource) dataSource).getUrl();
}
return null;
}
private String getSQLDatabase() {
// String dbName = RouteDataSourceContext.getRouteKey();
String dbName = null; //根据设置的多数据源修改此处,获取dbname
if (dbName == null) {
dbName = "DEFAULT";
}
String url = CatMybatisPlugin.sqlURLCache.get(dbName);
if (url != null) {
return url;
}
url = this.getSqlURL();//目前监控只支持mysql ,其余数据库需要各自修改监控服务端
if (url == null) {
url = String.format(EMPTY_CONNECTION, dbName);
}
CatMybatisPlugin.sqlURLCache.put(dbName, url);
return url;
}
/**
* 解析sql语句
* @param configuration
* @param boundSql
* @return
*/
public String showSql(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(getParameterValue(parameterObject)));
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
} else if (boundSql.hasAdditionalParameter(propertyName)) {
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
}
}
}
}
return sql;
}
/**
* 参数解析
* @param obj
* @return
*/
private String getParameterValue(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) {
}
}
package com.kye.map.ucenter.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
@Configuration
public class MybatisConfig implements EnvironmentAware {
private Environment environment;
@Bean
public DataSource getDateSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(environment.getProperty("spring.datasource.url"));
dataSource.setUsername(environment.getProperty("spring.datasource.username"));
dataSource.setPassword(environment.getProperty("spring.datasource.password"));
dataSource.setMaxActive(10);
dataSource.setDriverClassName(environment.getProperty("spring.datasource.driverClassName"));
dataSource.setMaxIdle(5);
return dataSource;
}
@Bean
public SqlSessionFactory getSqlSession(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
CatMybatisPlugin catMybatisPlugin = new CatMybatisPlugin();
factoryBean.setPlugins(new Interceptor[]{catMybatisPlugin});
Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath*:mappers/**/*.xml");
factoryBean.setMapperLocations(resources);
SqlSessionFactory sessionFactory = factoryBean.getObject();
return sessionFactory;
}
@Bean
public MapperScannerConfigurer getMapperScannerConfigurer(SqlSessionFactory sqlSessionFactory){
MapperScannerConfigurer configurer = new MapperScannerConfigurer();
configurer.setBasePackage("com.kye.map.ucenter.domain.mappers");
configurer.setSqlSessionFactory(sqlSessionFactory);
return configurer;
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
//
// @Bean
// @Override
// public PlatformTransactionManager annotationDrivenTransactionManager() {
// return new DataSourceTransactionManager(dataSource);
// }
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}
只需要在需要拦截的方法上加上@CatAnnotation 即可 type和value的值可以自定义
@Override
@CatAnnotation(type = "ServiceGetById",value = "getById")
public Resource getById(String resourceId) {
return resourceMapper.get(resourceId);
}