1. 程式人生 > >springboot專案整合大眾點評cat

springboot專案整合大眾點評cat

什麼是CAT
Cat是基於Java開發的實時應用監控平臺,為美團點評提供了全面的實時監控告警服務
• CAT作為服務端專案基礎元件,提供了java, c/c++, node, python, go等多語言客戶端,已經在美團點評的基礎架構中介軟體框架(MVC框架,RPC框架,資料庫框架,快取框架等,訊息佇列,配置系統等)深度整合,為美團點評各業務線提供系統豐富的效能指標、健康狀況、實時告警等。
• CAT很大的優勢是它是一個實時系統,CAT大部分系統是分鐘級統計,但是從資料生成到服務端處理結束是秒級別,秒級定義是48分鐘40秒,基本上看到48分鐘38秒資料,整體報表的統計粒度是分鐘級;第二個優勢,監控資料是全量統計,客戶端預計算;鏈路資料是取樣計算。
Cat的產品價值


• 減少線上問題的發現時間
• 減少問題故障的定位時間
• 輔助應用程式的優化工具
Cat的優勢
• 實時處理:資訊的價值會隨時間銳減,尤其是事故處理過程中。
• 全量資料:最開始的設計目標就是全量採集,全量的好處有很多。
• 高可用:所有應用都倒下了,需要監控還站著,並告訴工程師發生了什麼,做到故障還原和問題定位。
• 故障容忍:CAT 本身故障不應該影響業務正常運轉,CAT 掛了,應用不該受影響,只是監控能力暫時減弱。
• 高吞吐:要想還原真相,需要全方位地監控和度量,必須要有超強的處理吞吐能力。
• 可擴充套件:支援分散式、跨 IDC 部署,橫向擴充套件的監控系統。
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的的核心過濾器
 * Author: [email protected]
 * Date: 2018/10/24 15:34
 * Copyright (c)  跨越新科技 版權所有
 */
@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.