CAT实时监控系统:cat-client埋点设计

1.相关概念

如下在Cat中我们称为: logview消息树(或者MessageTree),即应用内部的调用链路。可参考:https://www.jianshu.com/p/855207522411

图1

2.整体设计介绍

Cat整体处理流程如下图所示:
(cat-server对应开源的cat-consumer模块)

图2

1. 业务接入cat-client,进行Cat埋点

2. cat-client将埋点信息,组织成logview消息树,上报到后端cat-sever服务器(通过tcp方式上报)

3. cat-server端接受到logview,将logview放到不同的线程里进行处理

3.1 Transaction线程,提取logview中transaction数据,构造transaction报表。

3.2 Event线程,提取logview中event数据,构造event报表

3.3 Problem线程,提取logview中problem数据,构造problem报表

3.4 Logview线程,将logview进行存储。

4. 查询端通过指定报表,查询相应数据。

3.logView树构造

logView树(也叫 消息树、messageTree):在同一个线程里,通过transaction容器,将Cat产生的各个埋点进行串联,形成的进程内部的调用链路。

埋点示例一:

public void test() throws InterruptedException {
        Cat.initializeByDomain("cat.test");

        Transaction t1 = Cat.newTransaction("method", "test");
        try{
            test2("dpg");
        }catch (Exception e){
            Cat.logError(e);
            t1.setStatus(e); //设置transaction状态
        }finally {
            t1.complete();   //transaction一定要complete,一般放到finally来保证
        }
        
        //cat数据上报是异步的,需要sleep一段时间等待数据上报,在结束进程
        Thread.sleep(3000);
 }
    
private void test2(String param) throws Exception {
        Cat.logEvent("test2param", param);

        Transaction t2 = Cat.newTransaction("method2", "test2");
        //中间执行业务逻辑
        Thread.sleep(10);
        t2.setSuccessStatus();
        t2.complete();

        throw new Exception("error");
 }

生成的logView如下所示:

图3

埋点示例二:

public void test() throws InterruptedException {
        Cat.initializeByDomain("cat.test");

        Transaction t1 = Cat.newTransaction("method", "test");
        try{
            test2("dpg");
        }catch (Exception e){
            Cat.logError(e);
            t1.setStatus(e); //设置transaction状态
        }finally {
            t1.complete();   //transaction一定要complete,一般放到finally来保证
        }

        Transaction t3 = Cat.newTransaction("DbMethod", "DbTest");
        Cat.logEvent("db", "mysql");
        t3.complete();
        //cat数据上报是异步的,需要sleep一段时间等待数据上报,在结束进程
        Thread.sleep(3000);
    }

    private void test2(String param) throws Exception {
        Cat.logEvent("test2param", param);

        Transaction t2 = Cat.newTransaction("method2", "test2");
        //中间执行业务逻辑
        Thread.sleep(10);
        t2.setSuccessStatus();
        t2.complete();

        throw new Exception("error");
    }

会生成2个logView树,如下图所示:

图4

图5

4.原理解析

你的埋点会被cat-cliet 串街成一个logview消息树,具体实现原理如下:

(t:代表transaction, E:代表Event)(t:transaction开始,T:transaction结束)

图6

1. 我们使用ThreadLocal变量,当new 一个Transaction1,会向stack push,并构造 Logview 消息树

2. Transaction2 new的时候,也会向stack push,并将Trasaction2 放到 当前logView 消息树中

3. Event new的时候,会将Event放到当前logView消息树中

4. Transaction2 complete,会从stack堆栈pop,并记录transaction2的耗时时间、状态。

5. Transactin1 complete,会从stack堆栈pop,并记录transaction1的耗时时间、状态。发现此时当前stack为空,就会将当前构建好的Logview消息树发送出去

6. 当代码流程在有Cat 埋点,就会走上面同样逻辑。 (最顶层通过Transaction树组织起来)

4.1 注意

1. Transaction一定要complete,并且不能跨线程comoplete(transaction的生成和complete需要在同一个线程里)

a. transaction只有complete我们才会发送整个logview消息树,如果你不complete,消息树就不会发送,挤压在内存里,造成监控数据不准。进而引发内存泄漏风险。

b. 由于cat-client内部使用ThreadLocal变量,所以需要在同一个线程进行new 和complete,否则complete会失效。

2. transaction complete顺序,和new的顺序相反。最先new的transaction,最后complete

a. transaction是一个堆栈结构,需要complete 和new 相互对应。

b. 如果,new的顺序是 a b c。 complete的顺序 也是 a b c。那么在complete a的时候,此时堆栈内容是 c、b、a,

b1. 我们会从stack pop一个对象,发现是c,不是a本身,就会把c complete。

b2. 继续stack pop一个对象,发现是 b,不是a本身,就会把 b complete。

b3. 继续stack pop 一个对象,发现是a本身,就会把a complete

b4. 之后b、c complete,就不会产生任何动作。

因为b、c被提前complete,所以会导致耗时统计不准。

5. 采样与聚合

1. 使用Cat埋点,cat会在客户端将埋点串成logview消息树上报到cat后端,进而解析成相应报表。

2. 当应用流量变得很大,埋点生成消息树也会成爆发行增长,为了不影响客户端机器性能,以及降低网络流量,我们会对cat埋点的消息树,进行采样聚合。

采样:只将部分logview 原样上报,减少上报量。比如采样率:1%, 100个logview消息树,只会上报一个。

聚合:将未被采样到的logview消息树,进行聚合、编码变成一个消息树 上报,保证报表统计的准确性。

如下图所示,cat-client会将近3s未被采样到的logview消息树,聚合成一个消息树。(被采样到的logview会被原样上报)

在生成的新消息树里,会统计每个transaction/event 次数、耗时。这样就可以把很多logview 合并成一个logview,极大减少了网络流量和消息量。(多个logview被聚合成一个logview上报,丢失了调用链路关系,但是指标数量信息没有丢失)

图7

5.1 如何判断一个链路是否被聚合

比如在Cat上查看一个链路,如下所示:

最顶层的埋点是System TransactionAggregator 或者 System EventAggregator 或则System _CatMergeTree(低版本) 则代表这个链路是被聚合的,不是真实的链路。 聚合的链路只是将:埋点的Transaction和Event 次数信息统计上报上来,为了保证Transaction、Event报表统计的准确性,不代表真实的链路

图8

5.2 如何查看被采样到的链路

problem报表都是采样到的链路。对于耗时长(比如耗时长的rpc、db、缓存调用)、失败的链路我们会在problem报表展示,所以在problem报表可以看到有问题的采样链

6.截断

对于采样命中的logview,我们会将整个logview发送到cat后端,进行存储。

对于有些业务,单个logview大小可能很多(比如:在一个循环里加了cat埋点),为了保证logview整个传输存储的成功率,我们会对logview大小进行限制。

如果发现logview埋点个数(Transaction+Event数量)超过2000,我们会将logview进行截断,以2000个大小作为限制进行截断。

图9

如果点击logview如上图所示,有个 RootLogview、ParentLogview的超链接,那么这个logview就是被截断的。

比如原始的logview,因为过长被截成5段。

其中:RootLogview:链接到第一段logview

ParentLogview:连接到上一个被截断后的logview

6.1 截断原理

图10

a. 如上图所示,当前我们处于1这个阶段,此时内存构造的logview树状态是A(其中,t:代表 开始一个Transaction,T:代表结束一个Transaction,E:代表一个Event或者Error)

b. 当我们继续new 一个Transctioin t7时,会检查当前内存中logview的长度,如果长度超过2000限制(即:Transaction + Event + Error 数量),开始对logview进行截断,进入阶段2

c. 在阶段2,我们会把已经complete的Transaction(t3、t4)打包在一起,构造一个新的完整logview A1 发送(当然发送是异步,不会阻塞进程)

d. 没有complete的Transaction(t1、t2、t5、t6)会再次组装在一起,变成A2,继续留在内存里 ,这样当new Transaction t7,t7被加入到A2中,进入到阶段3,继续等待新的埋点数据加入

e. 直到整个t1 这个transaction complete,整个logview才会发送走

6.2 截断问题

a. 统计次数不准:根据上面所述,在截断时, t1、t2、t5、t6 其实统计数据会不准。因为在A1中发送过一次,在A2中还会发送一次(当然截断次数越多,统计数值偏差越大)。不过为了修复统计次数不准,我们不会统计t1次数,知道最后一次阶段上报才会统计t1,其实这么做也只是解决了t1统计不准确问题。具体为什么不修复其他transaction统计不准问题,只能说太复杂,不好搞

b. 耗时统计不准:被阶段后,同一个transaction被放到2个logview里了(比如t2),这样耗时就被拆分成2部分,所以平均耗时,会偏低

6.3 截断时机:

1. transaction+event+error数量超过 2000

2. logview 跨小时超过10s

你可能感兴趣的:(CAT实时监控系统:cat-client埋点设计)