深度解析Cat源码系列专栏点击访问
持续更新中
文章目录
CAT源码分析 - 客户端(上-消息结构)
由于cat-client目录下的代码官方已经不再维护更新,所以分析lib目录下的cat客户端源码
1. 官方Demo
CAT客户端的源码分析以官方的使用QuickStart入手
如上图代码所示:
- 通过Cat客户端开启了一个Transaction,记录整个方法的运行时间。
- 在过程中“记录”(插入)了成功的Event事件,和两个Metric业务指标。
- 最后设置Transaction成功
SUCCESS
,标记结束complete()
。
2. 客户端初始化
CAT客户端使用时,不需要显式的预初始化,而是在客户端调用时再“懒”初始化。
Cat
类是一个单例,封装了客户端对Message消息的一些列API操作。
以newTransation方法为例分析代码:
- 首先获取MessageProcuder,通过MessageProducer来创建Transaction。
- MessageProducer是一个消息提供类,包括了消息的创建、上下文管理、初始化等逻辑。
注:这段代码可以看到NullMessage.TRANSACTION,包括getProducer()的实现,都使用了“空对象”模式,来减少空判断。这种模式在cat源码中大量使用。具体介绍-空对象模式
public static Transaction newTransaction(String type, String name) {
if (isEnabled()) {
// 判断客户端是否生效,初始化失败会为false
try {
// 获取Producer工程,
return Cat.getProducer().newTransaction(type, name);
} catch (Exception e) {
errorHandler(e);
return NullMessage.TRANSACTION;
}
} else {
return NullMessage.TRANSACTION;
}
}
public static MessageProducer getProducer() {
try {
// 检查是否执行初始化
checkAndInitialize();
if (producer != null) {
// 返回producer
return producer;
} else {
return NullMessageProducer.NULL_MESSAGE_PRODUCER;
}
} catch (Exception e) {
errorHandler(e);
return NullMessageProducer.NULL_MESSAGE_PRODUCER;
}
}
- 初始化方法校验是否已经初始化,之后通过Java SPI读取用户是否设置了自定义的ClientConfigProvider实现。这为用户提供了自定义配置和获取配置的方式。
默认情况下,使用CAT默认的配置进行初始化。
private static void checkAndInitialize() {
try {
// 由于是懒加载,避免重复初始化
if (!init) {
// 通过SPI读取自定义的配置
ClientConfig clientConfig = getSpiClientConfig();
if (clientConfig == null) {
// 执行初始化
initializeInternal();
} else {
initializeInternal(clientConfig);
}
}
} catch (Exception e) {
errorHandler(e);
}
}
private static ClientConfig getSpiClientConfig() {
// 通过SPI接口,获取/META-INF/service下是否有自定义配置
ServiceLoader<ClientConfigProvider> clientConfigProviders = ServiceLoader.load(ClientConfigProvider.class);
if (clientConfigProviders == null) {
return null;
}
Iterator<ClientConfigProvider> iterator = clientConfigProviders.iterator();
if (iterator.hasNext()){
//只支持一个ClientConfigProvider的实现,默认取查询结果第一个
ClientConfigProvider clientConfigProvider = (ClientConfigProvider)iterator.next();
return clientConfigProvider.getClientConfig();
} else {
return null;
}
}
真正的初始化逻辑com.dianping.cat.Cat#initializeInternal():
- 校验是否配置了app.name(/META-INFO/app.properties),作为Domain上报Server使用
- 单例双重检查是否初始化
- 引用MessageProducer实例和MessageManager实例(下文详解)
- 开启客户端通信TcpSocketSender
- 启动HeartBeat消息的检查线程
private static void initializeInternal() {
// 检查是否配置appname
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();
}
}
}
private static void validate() {
// 系统变量检查是否开启CAT客户端,默认开启
String enable = Properties.forString().fromEnv().fromSystem().getProperty("CAT_ENABLED", "true");
if ("false".equals(enable)) {
CatLogger.getInstance().info("CAT is disable due to system environment CAT_ENABLED is false.");
enabled = false;
} else {
// 检查是否从系统变量读取自定义的Domain
String customDomain = getCustomDomain();
// 没有特殊定义,默认从/META-INF/app.properties中读取app.name
if (customDomain == null && UNKNOWN.equals(ApplicationEnvironment.loadAppName(UNKNOWN)))