美团CAT源码分析4 - 客户端(下 消息通信)

本文深入解析了美团CAT客户端的消息通信任务,包括StatusUpdateTask的HeartBeat数据采集,TcpSocketSender的初始化与发送流程,以及LocalAggregator与DataUploader的角色。StatusUpdateTask收集JVM、ClassLoading和HttpStats等信息,通过TcpSocketSender基于Netty发送到服务端,同时客户端配置会定期刷新,确保连接的可靠性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

深度解析Cat源码系列专栏点击访问

CAT源码分析4 - 客户端(下 消息通信)

1. Client消息通信任务

Cat client端消息通信任务主要包括

  1. StatusUpdateTask - 客户端采集HeartBeat消息的任务线程
  2. TcpSocketSender - 基于Netty与Cat服务端保持长链接,发送MessageTree
  3. LocalAggregator.DataUploader - 汇聚信息(Transaction、Event、Metric)后的发送任务

它们在Cat客户端初始化时完成自身的启动
在这里插入图片描述

// com.dianping.cat.Cat#initializeInternal()
private static void initializeInternal() {
   
   
    validate();
    if (isEnabled()) {
   
   
        try {
   
   
            if (!init) {
   
   
                synchronized (instance) {
   
   
                    if (!init) {
   
   
                        producer = DefaultMessageProducer.getInstance();
                        manager = DefaultMessageManager.getInstance();
					
                        // 在符合条件的情况下,完成发送任务的初始化
                        StatusUpdateTask heartbeatTask = new StatusUpdateTask();
                        TcpSocketSender messageSender = TcpSocketSender.getInstance();
                        Threads.forGroup("cat").start(heartbeatTask);
                        Threads.forGroup("cat").start(messageSender);
                        Threads.forGroup("cat").start(new LocalAggregator.DataUploader());

                        CatLogger.getInstance().info("Cat is lazy initialized!");
                        init = true;
                    }
                }
            }
        } catch (Exception e) {
   
   
            errorHandler(e);
            disable();
        }
    }
}

2. StatusUpdateTask

StatusUpdateTask负责采集客户端的HeartBeat的相关数据,如系统的CPU利用率,内存情况等。采集后,通过该Task进行发送。
在这里插入图片描述

2.1 StatusExtension收集器

StatusUpdateTask在实例化时同时初始化,创建并注册多种HeartBeat数据的收集器。这些收集器都实现了 StatusExtension 接口来规范Collector的消息采集,最终StatusUpdateTask调用 getProperties 方法获取采集的数据。其实现类如下
实现类列表

public StatusUpdateTask() {
   
   
    initialize();
}
private void initialize() {
   
   
    try {
   
   
        // 注册JVM收集器
        JvmInfoCollector.getInstance().registerJVMCollector();
        // 状态收集器的注册管理器
        StatusExtensionRegister instance = StatusExtensionRegister.getInstance();
		// 执行注册
        instance.register(new StaticInfoCollector());
        instance.register(new ClassLoadingInfoCollector());
        instance.register(new ThreadInfoCollector());

        if (!isDocker()) {
   
   
            instance.register(new ProcessorInfoCollector());
        }
		// 默认对DB数据连接池进行监听
        if (Cat.isDataSourceMonitorEnabled()) {
   
   
            instance.register(new C3P0InfoCollector());
            instance.register(new DruidInfoCollector());
        }
		// http状态收集器,需要配合Cat-filter使用
        instance.register(new HttpStatsCollector());
    } catch (Exception e) {
   
   
        // ignore
    }
	// 记录使用到的java.lang.management.MemoryPoolMXBean,提供JVM信息
    logMemoryBean();
}

由于各个Collector通过不同的API接口获取想信息,但是主题逻辑相同——在getProperties方法被调用时,执行收集逻辑——因此,简单举例几个Collector,介绍部分核心逻辑

2.1.1 JVMCollector

JVMCollector负责收集JVM相关信息,注册和收集代码主要在com.dianping.cat.status.jvm.JvmInfoCollector类中。JvmInfoCollector是一个单例,其主要方法如下:

GC收集和Memory收集都使用java.lang.management.MemoryPoolMXBean提供的能力,不具体解释了

// 注册收集器
public void registerJVMCollector() {
   
   
    // 获取Status收集器的注册机
    final StatusExtensionRegister instance = StatusExtensionRegister.getInstance();
    // 注册一个gc收集器,此处通过包装,最终实现为collecotr
    instance.register(new AbstractCollector() {
   
   
        @Override
        public String getId() {
   
   
            return "jvm.gc";
        }
        @Override
        public Map<String, String> getProperties() {
   
   
            // 自定义collector的实现,执行gc收集
            Map<String, Number> map = collector.doGcCollect();
            // 转换map,主要是将map中的Number类型转换为String
            return convert(map);
        }
    });
	// 注册一个JVM内存收集器
    instance.register(new AbstractCollector() {
   
   
        @Override
        public String getId() {
   
   
            return "jvm.memory";
        }
        @Override
        public Map<String, String> getProperties() {
   
   
            // 执行memory收集
            Map<String, Number> map = collector.doMemoryCollect();
            return convert(map);
        }
    });
}
// GC信息收集
private Map<String, Number> doGcCollect() {
   
   
    long gcCount = 0;
    long gcTime = 0;
    long oldGCount = 0;
    long oldGcTime = 0;
    long youngGcCount = 0;
    long youngGcTime = 0;
    Map<String, Number> map = new LinkedHashMap<String, Number>();
	// 通过Java提供的GarbageCollectorMXBean能力,获取相关信息
    for (final GarbageCollectorMXBean garbageCollector : ManagementFactory.getGarbageCollectorMXBeans()) {
   
   
        gcTime += garbageCollector.getCollectionTime();
        gcCount += garbageCollector.getCollectionCount();
        String gcAlgorithm = garbageCollector.getName();
        // youngGcAlgorithm是代码中硬编码的一些配置算法
        if (youngGcAlgorithm.contains(gcAlgorithm)) {
   
   
            youngGcTime += garbageCollector.getCollectionTime();
            youngGcCount += garbageCollector.getCollectionCount();
        } else if (oldGcAlgorithm.contains(gcAlgorithm)) {
   
   
            oldGcTime += garbageCollector.getCollectionTime();
            oldGCount += garbageCollector.getCollectionCount();
        } else {
   
   
            Cat.logEvent("UnknownGcAlgorithm", gcAlgorithm);
        }
    }
    // 保存相关信息到map
    map.put("jvm.gc.count", gcCount - lastGcCount);
    map.put("jvm.gc.time", gcTime - lastGcTime);
    final long value = oldGCount - lastFullGcCount;
    if (value > 0) {
   
   
        hasOldGc = true;
    }
    map.put("jvm.fullgc.count", value);
    map.put("jvm.fullgc.time", oldGcTime - lastFullGcTime);
    map.put("jvm.younggc.count", youngGcCount - lastYoungGcCount);
    map.put("jvm.younggc.time", youngGcTime - lastYoungGcTime);
    if (youngGcCount > lastYoungGcCount) {
   
   
        map.put("jvm.younggc.meantime", (youngGcTime - lastYoungGcTime) / (youngGcCount - lastYoungGcCount));
    } else {
   
   
        map.put("jvm.younggc.meantime", 0);
    }
    lastGcCount = gcCount;
    lastGcTime = gcTime;
    lastYoungGcCount = youngGcCount;
    lastYoungGcTime = youngGcTime;
    lastFullGcCount = oldGCount;
    lastFullGcTime = oldGcTime;
    return map;
}
2.1.2 ClassLoadingInfoCollector

ClassLoadingInfoCollector通过ClassLoadingMXBean采集载入了哪些Class信息,核心代码如下

// 该方法在getProperties时调用,返回Map提供数据
private Map<String, Number> doClassLoadingCollect() {
   
   
    ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean();
    Map<String, Number> map = new LinkedHashMap<String, Number>();
    map.put("jvm.classloading.loaded.count", classLoadingMXBean.getLoadedClassCount());
    map.put("jvm.classloading.totalloaded.count", classLoadingMXBean.getTotalLoadedClassCount());
    map.put("jvm.classloading.unloaded.count", classLoadingMXBean.getUnloadedClassCount());
    return map;
}
2.1.3 HttpStatsCollector

使用HttpStats采集的数据进行汇总,HttpStats的数据由Cat-filter调用其doRequestStats方法进行写入。主要代码如下。

private Map<String, Number> doClassLoadingCollect() {
   
   
    Map<String, Number> map = new LinkedHashMap<String, Number>();
    // 通过httpStats获取数据
    HttpStats stats = HttpStats.getAndReset();
    map.put("http.count", stats.getHttpCount());
    map.put("http.meantime", stats.getHttpMeantime());
    map.put("http.status400.count", stats.getHttpStatus400Count());
    map.put("http.status500.count", stats.getHttpStatus500Count());
    return map;
}

// com.dianping.cat.status.http.HttpStats
public static synchronized HttpStats getAndReset() {
   
   
    HttpStats tmp = new HttpStats();
    HttpStats old = currentStatsHolder();
    current = tmp;
    // 返回当前的记录,并重置计数器
    return old;
}
// 添加计数逻辑
public void doRequestStats(long mills, int status) {
   
   
    try {
   
   
        if (is400(status)) {
   
   
            httpStatus400Count.incrementAndGet();
        } else if (is500(status)) {
   
   
            httpStatus500Count.incrementAndGet();
        }
        httpCount.incrementAndGet();
        httpTimeSum.addAndGet(mills);
    } catch (Exception e) {
   
   
        // ignore
    }
}

2.2 发送任务run

StatusUpdateTask执行run方法实现HeartBeat信息的发送,主要逻辑

  • await sleep一段时间,等待Cat客户端初始化完成
  • 获取本机IP并记录一个Reboot应用重启事件
  • 循环执行
    • 构建heartbeat消息
    • 刷新client配置,读取serverIp
    • 清理客户端缓存
    • 计算下次执行时间并等待,避免过分占用CPU
public void run() {
   
   
    // 等待初始化完成
    await();
    // 获取本机IP
    String localHostAddress = NetworkInterfaceManager.INSTANCE.getLocalHostAddress();
    // 记录应用Reboot事件
    Cat.logEvent("Reboot", localHostAddress, Message.SUCCESS, null);

    while (active) {
   
   
        // 构建消息
        buildHeartbeat(localHostAddress);
        // 刷新客户端配置
        refreshClientConfig();
        // 清理缓存
        clearCache();

        // 计算下次运行时间并等待等待1分钟下次运行
        try {
   
   
            Calendar cal = Calendar.getInstance(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值