CAT研究之使用控制台进行cat-home端排错

本文将以解决一个ping告警失败的问题为切入点,介绍如何利用CAT-Home端的日志来简化和加深CAT-HOME端的源码理解。

1. 概述

最近在测试Cat的告警功能时,惊讶地发现之前已经测试通过的ping告警功能居然无法成功重现了,这就很尴尬了!对于目前的我而言,CAT整体就是一个大黑盒。不出现明显的错误,愣是有种无从下手的感觉。

2. 思路

既然发现了问题,即使感觉无从下手,也必须强迫自己去面对。毕竟不能指望问题会自己把自己解决掉。

回想一下Cat-Home的设计思路,以及平时在进行Cat控制台操作时的所见所闻,大致可以作出如下判断:既然CAT-HOME作为Server运行时,同时也是作为一个Client不断向服务端上报自身的运行情况,那么应该可以在控制台上缩小问题的规模,进而最终定位和解决问题

3. 探究

正如本文开头部分提到的,这次我们就以ping告警失败作为切入点来进行探究。

从官方文档告警文档中得到的提示ping告警是一种HeartBeat检测,我们最终找到了cat-home项目中的ThirdPartyAlert类。

3.1 ThirdPartyAlert

在经过一番确认之后,我们基本可以认定就是这个类实现了ping告警的功能。接下来我们就对这个类的实现细节进行一些探究。
ThirdPartyAlert继承链

从以上的继承链来看,ThirdPartyAlert的实现还是相当简单的(这也符合CAT一直所秉承的开发原则——”简单的架构就是最好的架构”)。我们需要关注的重点应该只有其实现的java.lang.Runnable接口。

// ThirdPartyAlert实现的java.lang.Runnable接口
@Override
public void run() {
    // 启动时,保证线程休眠到下一个分钟的开始处, 才开始执行; 休眠的刻度以毫秒为单位
    boolean active = TimeHelper.sleepToNextMinute();

    while (active) {
        // 每分钟创建一个type为AlertThirdParty的 Transaction; 参见下方的图1
        Transaction t = Cat.newTransaction("AlertThirdParty", TimeHelper.getMinuteStr());
        // 开始执行前的时刻
        long current = System.currentTimeMillis();

        try {
            List<ThirdPartyAlertEntity> alertEntities = new ArrayList<ThirdPartyAlertEntity>();
            // m_entities中的Item最终来源属于另外一个类ThirdPartyAlertBuilder中的逻辑; 具体是 ThirdPartyAlertBuilder.HttpReconnector
            // 对于ping告警,一般是2次ping不通或者超时则告警; 具体原因正是这里, 首次ping失败会直接进行HttpReconnector的逻辑;如果依然ping不通,就会正式进入告警环节。
            while (m_entities.size() > 0) {
                ThirdPartyAlertEntity entity = m_entities.poll(5, TimeUnit.MILLISECONDS);

                alertEntities.add(entity);
            }
            // 按照上面的讲解, 代码逻辑执行到这里时, alertEntities字段中存放的正是本次需要告警的项目

            // 按Domain对ThirdParty Alert进行分组
            Map<String, List<ThirdPartyAlertEntity>> domain2AlertMap = buildDomain2AlertMap(alertEntities);

            for (Entry<String, List<ThirdPartyAlertEntity>> entry : domain2AlertMap.entrySet()) {
                String domain = entry.getKey();
                List<ThirdPartyAlertEntity> thirdPartyAlerts = entry.getValue();
                // 按Domain构建AlertEntity
                AlertEntity entity = new AlertEntity();

                entity.setDate(new Date()).setContent(thirdPartyAlerts.toString()).setLevel(AlertLevel.WARNING);
                entity.setMetric(getName()).setType(getName()).setGroup(domain);

                // 将本次需要告警的项目封装为统一的AlertEntity实例, 交由AlertManager进行统一的通知(sms/email/weixin)
                m_sendManager.addAlert(entity);
            }
            t.setStatus(Transaction.SUCCESS);
        } catch (Exception e) {
            t.setStatus(e);
            Cat.logError(e);
        } finally {
            t.complete();
        }

        // 计算执行任务的耗时
        long duration = System.currentTimeMillis() - current;

        try {
            // 如果耗时小于一分钟, 则休眠满一分钟; 否则直接进行下次调度
            // DURATION 的值为 一分钟的毫秒数 - 60 * 1000L
            if (duration < DURATION) {
                Thread.sleep(DURATION - duration);
            }
        } catch (InterruptedException e) {
            active = false;
        }
    }
}

图1(注意图中的45代表当前已经是一个小时里的第45分钟,所以该类Transaction已经累计了45条记录)
图1

3.2 ThirdPartyAlertBuilder

前面我们提到,本次需要进行告警的项目正是有此类产生的。而通过观察其继承链我们可以看到,关键性的方法依然是其实现的接口java.lang.Runnable

@Override
public void run() {
    boolean active = true;

    while (active) {
        // 本次循环开始的时刻
        long current = System.currentTimeMillis();
        // 一个专门的Transaction; 参见下方的图2
        Transaction t = Cat.newTransaction("ReloadTask", "AlertThirdPartyBuilder");

        try {
            // 此方法的逻辑就是,对于配置的请求地址,首次请求无正常响应, 则马上另起线程进行重连
            // 对于重连依然没有响应的, 则将其添加到ThirdPartyAlert实例中; 这样就和上面的逻辑契合了。
            buildAlertEntities(current);
            t.setStatus(Transaction.SUCCESS);
        } catch (Exception e) {
            t.setStatus(e);
            m_logger.error(e.getMessage(), e);
        } finally {
            t.complete();
        }

        long duration = System.currentTimeMillis() - current;

        try {
            if (duration < DURATION) {
                Thread.sleep(DURATION - duration);
            }
        } catch (InterruptedException e) {
            active = false;
        }
    }
}

图2
Transaction - ReloadTask

3.3 同一package下的其他类

3.3.1 HttpConnector

CAT正是使用此类来进行get/post请求,进而验证连通性。

3.3.2 ThirdPartyConfigManager

负责从数据库,或者默认的配置文件中读取thirdParty相关的配置信息。

// 数据库存放配置信息对应的name字段值
private static final String CONFIG_NAME = "thirdPartyConfig";

数据库截图
cat数据库

3.4 AlertManager

本类对于CAT-Home有着关键性的作用,告警的发送正是在此类中完成调度,

此类在实例化时,借助IOC容器即在后台进行着两个发送告警通知的任务。

// 实现的Initializable接口方法
@Override
public void initialize() throws InitializationException {
    // SendExecutor在其实现中回调AlertManager.send, 发送告警信息
    Threads.forGroup("cat").s在111tart(new SendExecutor());
    // RecoveryAnnouncer在其实现中回调AlertManager.sendRecoveryMessage, 发送恢复告警信息
    Threads.forGroup("cat").start(new RecoveryAnnouncer());
}

我们还可以在本类中找到所接收到的恢复告警邮件等的内容模板。
恢复告警邮件的内容模板

3.5 SenderManager

最终来看,AlertManager也是一个调度者,而将具体的告警发送的实现方式调度给了SenderManager

public boolean sendAlert(AlertChannel channel, AlertMessageEntity message) {
    String channelName = channel.getName();

    try {
        boolean result = true;
        String str = "nosend";

        // 如果本服务端负责发送告警
        if (m_configManager.isSendMachine()) {
            // 观察Sender的继承链, 就能看到mail, sms, weixin三种实现方式
            Sender sender = m_senders.get(channelName);

            result = sender.send(message);
            // 字段 str 应该反映的是 发送情况,即是否发送成功
            str = String.valueOf(result);
        }
        // 以后如果发现告警失败,我们就可以关注这个Event, 查看发送是否成功
        // 如果这个存在, 则表明警告已产生, 只是发送告警失败; 反之若不存在, 则代表告警都没有正常生成。
        Cat.logEvent("Channel:" + channelName, message.getType() + ":" + str, Event.SUCCESS, null);
        return result;
    } catch (Exception e) {
        Cat.logError(e);
        return false;
    }
}

言语过于抽象,遂贴上几幅图以方便理解。
Event-Channel
全部失败了

由以上两幅截图,我们可以确定警告已经如期产生,只是在最终的发送阶段除了差池。而在定位了以上问题之后,笔者很快就找到了问题的根源。轻松地排除了错误。

4. 补充

  1. CAT-Home的控制台不失为一个快速了解CAT服务端的捷径。我们可以通过使用IDE的查找功能快速定位感兴趣的细节。例如上面的“Channel:mail” Event, 我们就可以使用如下方式快速定位。注意这里的查找内容中,把前面的 " 带上可以大大缩小查找范围。
    快速定位

  2. 本文所讨论的相关类所在的com.dianping.cat.report.alert package 。 旗下的子package就分别属于一种类型告警。
    这里写图片描述

猜你喜欢

转载自blog.csdn.net/lqzkcx3/article/details/80729390
cat