一、美团大众点评CAT监控系列文章---CAT简介以及和springboot的集成

一、 什么是CAT

1.cat简介

Cat是基于Java开发的实时应用监控平台,为美团点评提供了全面的实时监控告警服务
• CAT作为服务端项目基础组件,提供了java, c/c++, node, python, go等多语言客户端,已经在美团点评的基础架构中间件框架(MVC框架,RPC框架,数据库框架,缓存框架等,消息队列,配置系统等)深度集成,为美团点评各业务线提供系统丰富的性能指标、健康状况、实时告警等。
• CAT很大的优势是它是一个实时系统,CAT大部分系统是分钟级统计,但是从数据生成到服务端处理结束是秒级别,秒级定义是48分钟40秒,基本上看到48分钟38秒数据,整体报表的统计粒度是分钟级;第二个优势,监控数据是全量统计,客户端预计算;链路数据是采样计算。

2.Cat的产品价值

• 减少线上问题的发现时间
• 减少问题故障的定位时间
• 辅助应用程序的优化工具

3.Cat的优势

• 实时处理:信息的价值会随时间锐减,尤其是事故处理过程中。
• 全量数据:最开始的设计目标就是全量采集,全量的好处有很多。
• 高可用:所有应用都倒下了,需要监控还站着,并告诉工程师发生了什么,做到故障还原和问题定位。
• 故障容忍:CAT 本身故障不应该影响业务正常运转,CAT 挂了,应用不该受影响,只是监控能力暂时减弱。
• 高吞吐:要想还原真相,需要全方位地监控和度量,必须要有超强的处理吞吐能力。
• 可扩展:支持分布式、跨 IDC 部署,横向扩展的监控系统。

4.CAT支持的监控消息类型包括

• Transaction 适合记录跨越系统边界的程序访问行为,比如远程调用,数据库调用,也适合执行时间较长的业务逻辑监控,Transaction用来记录一段代码的执行时间和次数。
• Event 用来记录一件事发生的次数,比如记录系统异常,它和transaction相比缺少了时间的统计,开销比transaction要小。
• Heartbeat 表示程序内定期产生的统计信息, 如CPU%, MEM%, 连接池状态, 系统负载等。
• Metric 用于记录业务指标、指标可能包含对一个指标记录次数、记录平均值、记录总和,业务指标最低统计粒度为1分钟。

二、cat客户端的集成步骤:

1.在需要被监控的项目里引入cat-client 的meven依赖:

		<dependency>
            <groupId>com.dianping.cat</groupId>
            <artifactId>cat-client</artifactId>
            <version>3.0.0</version>
        </dependency>

2.引入cat的核心过滤器:

在这里插入图片描述

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

3.在需要被监控的项目建立如下结构:

app.name=ucenter 这个必须有,cat服务端必须通过这个找到相应的项目
在这里插入图片描述

4.需要在你的项目的根目录建立如下结构的文件夹:

在这里插入图片描述

client.xml内容如下:

<?xml version="1.0" encoding="utf-8"?>

<config mode="client" xmlns:xsi="http://www.w3.org/2001/XMLSchema" xsi:noNamespaceSchemaLocation="config.xsd">
   <servers>
   	<!-- Local mode for development -->
   	<server ip="10.10.242.9" port="2280" http-port="8080" />
   	<!-- If under production environment, put actual server address as list. -->
   	<!-- 
   		<server ip="192.168.7.71" port="2280" /> 
   		<server ip="192.168.7.72" port="2280" /> 
   	-->
   </servers>
</config>

cat项目的日志目录:
)如果项目启动出问题,或者cat监控不到自己的项目,可以看看这里的日志)
在这里插入图片描述

5.集成mybatis拦截器(目前只能拦截到增删改)

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) {
   }

}

在这里插入图片描述

6.将mybatis拦截器注入到sqlSessionFactory

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;
    }
}

在这里插入图片描述

7.@CatAnnotation注解的使用:

只需要在需要拦截的方法上加上@CatAnnotation 即可 type和value的值可以自定义

  @Override
    @CatAnnotation(type = "ServiceGetById",value = "getById")
    public Resource getById(String resourceId) {
        return resourceMapper.get(resourceId);
    }

三、相关的参考文档

  • CAT项目的开源地址: https://github.com/dianping/cat
  • CAT官方站点:http://unidal.org/cat/r
  • 微盟的CAT接入文档:http://tx.cat.weimob.com/cat/doc.html
  • 参考文档:http://fanlychie.github.io/post/cat-setup.html

四、 系列文章

  1. 一、美团大众点评CAT监控系列文章—CAT简介以及和springboot的集成
  2. 二、美团大众点评CAT监控系列文章—CAT监控的服务端配置
  3. 三、美团大众点评CAT监控系列文章—Springboot集成CAT并实现邮件告警
  • 0
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

T-OPEN

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值