springboot接入大众点评Cat平台

3 篇文章 0 订阅
1 篇文章 0 订阅

1. Cat简介

CAT(Central Application Tracking)基于Java开发的实时监控平台,主要包括移动端监控,应用侧监控,核心网络层监控,系统层监控等。是一个提供实时监控报警,应用性能分析诊断的工具。
主要有如下功能:

  • 机器状态信息:CPU负载、内存信息、磁盘使用率等服务器信息及线程栈、堆、垃圾回收等应用进程信息;
  • 请求访问情况:请求个数、响应时间、处理状态等接口访问信息;
  • 异常情况:服务无响应、应用Exception等异常信息;
  • 业务情况:订单量统计,销售额等业务信息。

2. 应用架构图

springboot应用可以通过client将监控信息传递至Cat服务端处理,生成报表并启动告警。
应用架构图

3. 项目配置

  • 创建客户端路由配置
    在被监控应用服务器上创建目录/data/appdatas/cat//data/applogs/cat
    /data/appdatas/cat/下新增客户端路由文件client.xml,用于客户端连接Cat服务端,内容如下:
    <?xml version="1.0" encoding="utf-8"?>
    <config mode="client">
     <servers>
         <server ip="10.253.129.2" port="2280" http-port="8080"/>
         <server ip="10.253.129.8" port="2280" http-port="8080"/>
         <server ip="10.253.129.17" port="2280" http-port="8080"/>
     </servers>
    </config>
    
  • 依赖引入
    客户端下载链接:https://github.com/dianping/cat/blob/master/lib/java/jar/cat-client-3.0.0.jar
    也可以下载源代码自行编译:https://github.com/dianping/cat/tree/master/lib/java
    <dependency>
     <groupId>com.dianping.cat</groupId>
     <artifactId>cat-client</artifactId>
     <version>3.0.0</version>
    </dependency>
    
  • 应用配置
    resources目录下创建META-INF文件夹,在META-INF下创建app.properties,文件内容如下:
    app.name=XXXXX  #项目domain,应用自定义
    
  • 增加访问URL监控
    springboot应用增加如下文件即可(CatFilterConfigure.java
    package com.test.framework.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;
      }
    }
    
    效果如图所示
    在这里插入图片描述
  • 增加mybatis监控
    新增CatMybatisPlugin.java文件,内容如下:
    package com.test.framework.filter;
    
    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.cache.CacheKey;
    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;
    
    @Intercepts({@Signature(method = "query", type = Executor.class, args = {MappedStatement.class, Object.class,
                  RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.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) {
      }
    }
    
    修改mybatis配置文件,增加拦截器
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
          <property name="dataSource" ref="dataSource" />
          <property name="plugins">
              <array>
                  <bean class="com.test.framework.filter.CatMybatisPlugin"/> #拦截SQL
              </array>
          </property>
          <property name="mapperLocations" value="classpath:com/test/dao/**/*Mapper.xml"/>
      </bean>
    
    监控页面显示如下图在这里插入图片描述
  • 增加接口访问频次监控
    在springboot应用中新增CatMetricAop.java文件,开启aop代理配置即可,内容如下:
    package com.test.framework.aop;
    
    import com.dianping.cat.Cat;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    import javax.servlet.http.HttpServletRequest;
    
    @Component
    @Aspect
    @Order(2)
    public class CatMetricAop {
      private static final Logger LOGGER = LoggerFactory.getLogger(CatMetricAop.class);
    @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)||@annotation(org.springframework.web.bind.annotation.PostMapping)")
    
      public void controllerAspect() {
      }
      
      @Around("controllerAspect()")
      public Object around(ProceedingJoinPoint joinPoint) {
          Object value = null;
          try {
              String method = joinPoint.getSignature().getName();
              Cat.logMetricForCount(method);
              value = joinPoint.proceed();
          } catch (Throwable throwable) {
              LOGGER.error("CatMetricAop异常", throwable);
          }
          return value;
      }
    }
    
    监控页面显示如下图在这里插入图片描述
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值