深度解析Cat源码系列专栏点击访问
文章目录
CAT源码分析4 - 客户端(下 消息通信)
1. Client消息通信任务
Cat client端消息通信任务主要包括
- StatusUpdateTask - 客户端采集HeartBeat消息的任务线程
- TcpSocketSender - 基于Netty与Cat服务端保持长链接,发送MessageTree
- 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(