`
m635674608
  • 浏览: 5041534 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

CAT 使用小结

 
阅读更多

CAT具体的设计思想、实现原理在这我就不罗列了,本文主要是记录一下在使用CAT的过程中遇到的一些问题,比如分布式logview,Cache、DB埋点监控等,问题不多,但是比较典型。

(本文涉及的CAT版本为1.3.6)

1、分布式 logview 的日志树串联实现

目前使用过两种,一种是基于 dubbo 应用的 rpc 调用,一种是基于 http 请求的 rest 服务调用。首先说下 message tree 的实现,追踪跨服务的消息时,通过根消息 id 和父级消息 id 及子消息 id 三个属性进行消息串联,组成消息树。关键点在 tree 的三个 id 的获得和传递。

这里有两点,第一是 CAT 消息树生成原理

我们需要实现 Cat 的 Context 上下文,然后通过 Cat.logRemoteCallClient(context) 生成包含节点数据的上下文对象(方法中通过创建消息树对象来获取各节点的消息 id,填充给上下文),当远程服务端接收到这个 context ,使用 Cat.logRemoteCallServer(context) 方法,读取各节点消息 id,组建消息树。

第二是消息应如何传递

dubbo 应用的 rpc 调用方式:调用过程要传递的 rpc 上下文,其中包含调用信息、参数以及状态信息等,可以把消息 id 信息放到 RpcContext 中,然后通过调用 Invocation 对象的 invoke 方法,将消息传递至服务端。最后,通过dubbo的 spi 拓展机制,实现 com.alibaba.dubbo.rpc.Filter,用来获取 rpcContext 的内容。

rest 风格的http请求方式:调用时,在服务请求方把消息id信息放到 Http-Header 中,在服务提供方,用filter 拦截,并获得 http-header 中的消息 id,这样通过埋点,串联起消息树。

废话不多说了,上码吧。

1).dubbo 调用方式部分实现(首先要清楚 dubbo 的 spi 相关配置,CAT监控的配置等)

public class DubboCatFilter implements Filter {

    private static final ThreadLocal<Cat.Context> CAT_CONTEXT = new ThreadLocal<Cat.Context>();

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        URL url = invoker.getUrl();
        String sideKey = url.getParameter(Constants.SIDE_KEY);
        String loggerName = invoker.getInterface().getSimpleName() + "." + invocation.getMethodName();
        String type = "PigeonCall";
        if (Constants.PROVIDER_SIDE.equals(sideKey)) {
            type = "PigeonService";
        }
        Transaction t = Cat.newTransaction(type, loggerName);
        Result result = null;
        try {
            Cat.Context context = getContext();
            if (Constants.CONSUMER_SIDE.equals(sideKey)) {
                createConsumerCross(url, t);
                Cat.logRemoteCallClient(context);
            } else {
                createProviderCross(url, t);
                Cat.logRemoteCallServer(context);
            }
            setAttachment(context);
            result = invoker.invoke(invocation);

            if (result.hasException()) {
                //给调用接口出现异常进行打点
                Throwable throwable = result.getException();
                Event event = null;
                if (RpcException.class == throwable.getClass()) {
                    Throwable caseBy = throwable.getCause();
                    if (caseBy != null && caseBy.getClass() == TimeoutException.class) {
                        event = Cat.newEvent("DUBBO_TIMEOUT_ERROR", loggerName);
                    } else {
                        event = Cat.newEvent("DUBBO_REMOTING_ERROR", loggerName);
                    }
                } else if (RemotingException.class.isAssignableFrom(throwable.getClass())) {
                    event = Cat.newEvent("DUBBO_REMOTING_ERROR", loggerName);
                }else{
                    event = Cat.newEvent("DUBBO_BIZ_ERROR", loggerName);
                }
                event.setStatus(result.getException());
                completeEvent(event);
                t.addChild(event);
                t.setStatus(result.getException().getClass().getSimpleName());
            } else {
                t.setStatus(Message.SUCCESS);
            }
            return result;
        } catch (RuntimeException e) {
            Event event = null;
            if (RpcException.class == e.getClass()) {
                Throwable caseBy = e.getCause();
                if (caseBy !=null && caseBy.getClass() == TimeoutException.class) {
                    event = Cat.newEvent("DUBBO_TIMEOUT_ERROR", loggerName);
                } else {
                    event = Cat.newEvent("DUBBO_REMOTING_ERROR", loggerName);
                }
            } else {
                event = Cat.newEvent("DUBBO_BIZ_ERROR", loggerName);
            }
            event.setStatus(e);
            completeEvent(event);
            t.addChild(event);
            t.setStatus(e.getClass().getSimpleName());
            if (result == null) {
                throw e;
            } else {
                return result;
            }
        } finally {
            t.complete();
            CAT_CONTEXT.remove();
        }
    }

    static class DubboCatContext implements Cat.Context {
        private Map<String,String> properties = new HashMap<String, String>();

        @Override
        public void addProperty(String key, String value) {
            properties.put(key,value);
        }

        @Override
        public String getProperty(String key) {
            return properties.get(key);
        }
    }

    private void setAttachment(Cat.Context context) {
        RpcContext.getContext().setAttachment(Cat.Context.ROOT,context.getProperty(Cat.Context.ROOT));
        RpcContext.getContext().setAttachment(Cat.Context.CHILD,context.getProperty(Cat.Context.CHILD));
        RpcContext.getContext().setAttachment(Cat.Context.PARENT,context.getProperty(Cat.Context.PARENT));
    }

    private Cat.Context getContext(){
        Cat.Context context = CAT_CONTEXT.get();
        if (context==null) {
            context = initContext();
            CAT_CONTEXT.set(context);
        }
        return context;
    }

    private Cat.Context initContext() {
        Cat.Context context = new DubboCatContext();
        Map<String,String> attachments = RpcContext.getContext().getAttachments();
        if (attachments!=null&&attachments.size()>0) {
            for (Map.Entry<String,String> entry:attachments.entrySet()) {
                if (Cat.Context.CHILD.equals(entry.getKey()) || Cat.Context.ROOT.equals(entry.getKey()) || Cat.Context.PARENT.equals(entry.getKey())) {
                    context.addProperty(entry.getKey(),entry.getValue());
                }
            }
        }
        return context;
    }

    private void createConsumerCross(URL url, Transaction t) {
        Event crossAppEvent = Cat.newEvent("PigeonCall.app", getProviderAppName(url));
        Event crossServerEvent = Cat.newEvent("PigeonCall.server", url.getHost());
        Event crossPortEvent = Cat.newEvent("PigeonCall.port", url.getPort() + "");
        crossAppEvent.setStatus(Event.SUCCESS);
        crossServerEvent.setStatus(Event.SUCCESS);
        crossPortEvent.setStatus(Event.SUCCESS);
        completeEvent(crossAppEvent);
        completeEvent(crossPortEvent);
        completeEvent(crossServerEvent);
        t.addChild(crossAppEvent);
        t.addChild(crossPortEvent);
        t.addChild(crossServerEvent);
    }

    private void createProviderCross(URL url, Transaction t) {
        String consumerAppName = RpcContext.getContext().getAttachment(Constants.APPLICATION_KEY);
        if (StringUtils.isEmpty(consumerAppName)) {
            consumerAppName = RpcContext.getContext().getRemoteHost() + ":" + RpcContext.getContext().getRemotePort();
        }
        Event crossAppEvent = Cat.newEvent("PigeonService.app", consumerAppName);
        Event crossServerEvent = Cat.newEvent("PigeonService.client", url.getHost());
        crossAppEvent.setStatus(Event.SUCCESS);
        crossServerEvent.setStatus(Event.SUCCESS);
        completeEvent(crossAppEvent);
        completeEvent(crossServerEvent);
        t.addChild(crossAppEvent);
        t.addChild(crossServerEvent);
    }

    private void completeEvent(Event event) {
        AbstractMessage message = (AbstractMessage) event;
        message.setCompleted(true);
    }

}

2).http-restful 调用方式部分实现

CatHttpClientProxy.java

public void requestByGet(String url) {
    Transaction t = Cat.newTransaction("PigeonCall", "method000");
    
    //创建默认的httpClient实例
    CloseableHttpClient httpClient = HttpClients.createDefault();
    RequestConfig requestConfig = RequestConfig.custom()  
                .setConnectTimeout(5000).setConnectionRequestTimeout(1000)  
                .setSocketTimeout(5000).build();
    try {
        HttpGet httpGet = new HttpGet(url);
        httpGet.setConfig(requestConfig);
            
        //串联埋点
        Cat.Context context = new CatHttpContext();
        this.createConsumerCross(url, t);
        Cat.logRemoteCallClient(context);
        httpGet.setHeader(Cat.Context.ROOT, context.getProperty(Cat.Context.ROOT));
        httpGet.setHeader(Cat.Context.PARENT, context.getProperty(Cat.Context.PARENT));
        httpGet.setHeader(Cat.Context.CHILD, context.getProperty(Cat.Context.CHILD));
            
        System.out.println("执行get请求:...." + httpGet.getURI());
        CloseableHttpResponse httpResponse = null;
        //发送get请求
        httpResponse = httpClient.execute(httpGet);//请求返回的Resp,含http的header和执行结果实体Entity
        try {
            //response实体
            HttpEntity entity = httpResponse.getEntity();//不包含header
            if (null != entity) {
                System.out.println("响应状态码:"+ httpResponse.getStatusLine());
                System.out.println("-------------------------------------------------");
                System.out.println("响应内容:" + EntityUtils.toString(entity));
            }
        } finally {
            httpResponse.close();
        }
        t.setStatus(Transaction.SUCCESS);
    } catch (Exception e) {
        e.printStackTrace();
        t.setStatus(e.getClass().getSimpleName());
    } finally {
        t.complete();
        try {
            closeHttpClient(httpClient);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

private void createConsumerCross(String url, Transaction t){
    Event crossAppEvent = Cat.newEvent("PigeonCall.app", "serverName");
    Event crossServerEvent = Cat.newEvent("PigeonCall.server", "serverIp");
    Event crossPortEvent = Cat.newEvent("PigeonCall.port", "serverPort");
    crossAppEvent.setStatus(Event.SUCCESS);
    crossServerEvent.setStatus(Event.SUCCESS);
    crossPortEvent.setStatus(Event.SUCCESS);
    completeEvent(crossAppEvent);
    completeEvent(crossPortEvent);
    completeEvent(crossServerEvent);
    t.addChild(crossAppEvent);
    t.addChild(crossPortEvent);
    t.addChild(crossServerEvent);
}

private void completeEvent(Event event){
    AbstractMessage message = (AbstractMessage) event;
    message.setCompleted(true);
}

private void closeHttpClient(CloseableHttpClient client) throws IOException{
    if (client != null) {
        client.close();
    }
}

2、CAT对 redis 缓存进行详细监控

CAT源码内部对于缓存的识别存在一个 convention 约定,是基于匹配 “Cache.” 字符串的,并且通过判断字符串 “Cache.memcached” 来支持 memcached 监控,可是没有对 redis 做显示支持,需要修改源码,增加判断字符串 “Cache.redis”;

1).修改类:cat-home - com.dianping.cat.report.page.statistics.task.utilization.TransactionReportVisitor.java

增加对 redis 的判断支持:

private static final String REDIS = "Cache.redis";
public TransactionReportVisitor() {
	m_types.add("URL");
	m_types.add("Service");
	m_types.add("PigeonService");
	m_types.add("Call");
	m_types.add("PigeonCall");
	m_types.add("SQL");
	m_types.add(MEMCACHED);
	m_types.add(REDIS);
}
@Override
public void visitType(TransactionType type) {
	String typeName = type.getId();
	Domain domain = m_report.findOrCreateDomain(m_domain);
  
	if ("Service".equals(typeName)) {
		typeName = "PigeonService";
	} else if ("Call".equals(typeName)) {
		typeName = "PigeonCall";
	} else if (typeName.startsWith(MEMCACHED)) {
		typeName = MEMCACHED;
	} else if (typeName.startsWith(REDIS)){
		typeName = REDIS;
	}
	......
}

2).修改类:cat-core - com.dianping.cat.config.server.ServerConfigManager.java

增加对 redis 的判断支持:

public boolean isCacheTransaction(String type) {
	return StringUtils.isNotEmpty(type) && (type.startsWith("Cache.memcached") || type.startsWith("Cache.redis"));
}

3).修改类:cat-consumer - com.dianping.cat.consumer.storage.StorageAnalyzer.java

增加对redis的判断支持:

private void processCacheTransaction(MessageTree tree, Transaction t) {
	String cachePrefix = "Cache.";
	String ip = "Default";
	String domain = tree.getDomain();
	String cacheType = t.getType().substring(cachePrefix.length());
	String name = t.getName();
	String method = name.substring(name.lastIndexOf(":") + 1);
	List<Message> messages = t.getChildren();

	for (Message message : messages) {
		if (message instanceof Event) {
			String type = message.getType();

			if (type.equals("Cache.memcached.server") || type.equals("Cache.redis.server")) {
				ip = message.getName();
				int index = ip.indexOf(":");
				if (index > -1) {
					ip = ip.substring(0, index);
				}
			}
		}
	}
	......
}

3、CAT 对 DB 数据库进行详细监控

如果你的 orm 框架使用的 mybatis,可以考虑通过实现拦截器 Interceptor 来DB进行底层监控,CAT对数据库的埋点也存在 convention,这里代码中存在 hard code。具体埋点如下:

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类型
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
Cat.logEvent("SQL.Method", sqlCommandType.name().toLowerCase());

String JDBC_CONNECTION = "jdbc:mysql://unknown:3306/%s?useUnicode=true";
Cat.logEvent("SQL.Database", String.format(JDBC_CONNECTION, serverIp, dbName));

spring配置如下:

<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:mybatis.xml"/>
    <!-- 插件配置 -->
    <property name="plugins">
        <array>
            <bean class="com.kubbo.java.common.cat.CatMybatisPlugin"></bean>
        </array>
    </property>
</bean>

以上仅罗列了每个问题的一种实现方案,只是给正在研究CAT的同学一个参考思路,个人研究CAT也是刚开始,所说之处不免存在一些纰漏,欢迎指正和交流。

 

http://my.oschina.net/u/129971/blog/688371

分享到:
评论

相关推荐

    美团cat在springboot项目中的使用

    在Spring Boot项目中使用CAT监控系统,主要通过在项目中引入CAT的依赖包、添加CAT拦截器到请求处理链,以及部署并配置CAT服务端来实现。使用过程中应当注意服务端的网络配置,以及逐步完善监控系统,以便更高效地...

    Cat技术入门总结

    ### Cat技术入门总结 #### 一、概述 **1. CAT是什么** - **定义**:CAT是一款由大众点评开发的高性能实时应用监控系统。它主要用于监控Java应用程序,但同时也支持.NET等多种语言版本,能够与多种流行框架集成,...

    Cat 客户端埋点插件用户手册1

    总结,Cat客户端埋点插件通过无侵入的方式,全面覆盖了各种常见的操作和组件,为系统监控和性能优化提供了强大的支持。开发者可以根据实际需求选择合适的埋点模块,实现对应用程序的深度监控。同时,随着项目的迭代...

    TwinCAT3和第三方伺服电机——汇川电机使用

    总结来说,TwinCAT3与汇川电机的集成,借助于高效的通信协议和强大的编程环境,能够实现高效、精确的运动控制,是现代工业自动化领域的理想选择。通过深入理解TwinCAT3的功能和汇川电机的特性,工程师可以充分发挥...

    导航雷达CAT240_asterix-cat240-v1.1_雷达_

    压缩包内的文件“asterix-cat240-v1.1.pdf”很可能是一个详细的规范文档,包含CAT 240标准的定义、数据字段解释、示例和使用指导。阅读这份PDF文档,读者可以了解到如何处理和解码按照此标准编码的雷达数据。 而...

    TwinCAT2电子手册(中文).rar

    TwinCAT2电子手册(中文)为用户提供了全面、详尽的TwinCAT2软件使用指导,不仅涵盖了基础的PLC编程,还涉及到高级的运动控制和实时通信等复杂功能。配合中文帮助文件,使得中国用户能够更轻松地理解和应用这一强大...

    TwinCAT 2 PLC入门教程V2.42.pdf

    末尾设有本章小结,总结归纳重点信息, 读者也可以记录下学习过程中的心得感悟。 第一章介绍了 TwinCAT 2 软件、 Information System 帮助系统的安装; 第二章至第六章介绍了扫描硬件,软件编程下载等一系列基本...

    点评CAT服务cat.war

    总结来说,"点评CAT服务cat.war"主要涉及分布式系统监控、服务调用链路追踪、健康检查、业务指标监控等多个方面,是现代互联网企业不可或缺的运维工具。通过对`cat.war`及相关文件的部署和配置,我们可以实现对系统...

    Typora-catfish-master.zip

    总结来说,“Typora-catfish-master.zip”提供了Catfish主题,通过简单的几步操作,用户可以轻松地为Typora增添新的视觉体验。这款主题不仅美观,而且易于配置,无论是日常写作还是专业文档处理,都能为你带来更优质...

    CAT框架的服务端和客户端的配置

    总结,CAT框架在分布式系统的监控中起到了至关重要的作用,它能够帮助开发团队实时掌握系统的运行状况,快速定位问题,提升系统稳定性。正确配置和使用CAT,能显著提高运维效率,降低故障排查成本。在实际项目中,...

    TwinCAT 2 PLC入门教程V2.4_入门_V2_twincat2_twincat教程_

    总结,TwinCAT 2 PLC入门教程V2.4是一个全面的学习资源,涵盖了从基本概念、编程语言到具体应用的各个环节。通过深入学习和实践,初学者能够掌握TwinCAT 2的使用,从而在自动化工程领域发挥出强大的潜力。提供的PDF...

    TwinCat的特点

    总结,TwinCat2作为一款先进的自动化软件,其独立启动和停止、独立存储区、多语言编程、实时操作系统、强大的诊断工具、视觉化工程、丰富的通讯协议支持以及扩展功能等特性,使得它在工业自动化领域具有极高的应用...

    twincat3三种烧写EEPROM的方法.docx

    4. **Step4:** 使用TWINCAT内置的“Update EEPROM”功能进行烧写操作。可以通过两种方式进入烧写界面:一种是在设备列表中右键点击设备,选择相应的烧写选项;另一种是在项目管理器中选择烧写操作。选择需要烧写的...

    TwinCAT3 OPC-UA基础使用_TWINCAT3OPCUA_

    总结,TwinCAT3 OPC UA结合了TwinCAT3的强大控制能力与OPC UA的通信优势,为企业提供了高效、安全的工业自动化解决方案。掌握其基本使用方法,能有效提升设备间的互联互通性,促进生产效率的提升。

    Twincat2_x64_2.11

    总结来说,"Twincat2_x64_2.11"是倍福公司在64位系统上的一个重要创新,它的出现不仅优化了编程环境,提升了软件性能,还为企业提供了更高效、更灵活的自动化解决方案。对于开发者和工程师而言,掌握并熟练应用Twin...

    TwinCAT start时蓝屏\死机补丁

    总结来说,这个补丁是为了解决在Windows XP环境下启动TwinCAT服务时可能出现的系统崩溃问题,通过替换特定的驱动文件来提高系统兼容性和稳定性。用户在使用过程中,务必遵循提供的指南,以确保补丁能够正确安装并...

    springmvc-cat实时监控demo

    **Spring MVC与CAT实时监控...总结,这个DEMO是一个很好的起点,它将帮助你掌握Spring MVC与CAT的集成,从而提升你的项目监控能力。了解并熟练运用CAT,将有助于你在复杂的企业级环境中更好地管理和维护你的应用程序。

Global site tag (gtag.js) - Google Analytics