上一篇:CAT跨语言服务链监控(六)消息分析器与报表(一) 下一篇:CAT跨语言服务链监控(八)报表持久化
CrossAnalyzer-调用链分析
在分布式环境中,应用是运行在独立的进程中的,有可能是不同的机器,或者不同的服务器进程。那么他们如果想要彼此联系在一起,形成一个调用链,在Cat中,CrossAnalyzer会统计不同服务之间调用的情况,包括服务的访问量,错误量,响应时间,QPS等,这里的服务主要指的是 RPC 服务,在微服务监控中,这是核心。
在讲 CrossAnalyzer 的处理逻辑之前,我们先看下客户端的埋点的一个模拟情况。
一般情况下不同服务会通过几个ID进行串联。这种串联的模式,基本上都是一样的。在Cat中,我们需要3个ID:
- RootId,用于标识唯一的一个调用链
- ParentId,父Id是谁?谁在调用我
- ChildId,我在调用谁?
那么我们如何传递这些ID?Cat为我们提供了一个内部接口 Cat.Context,但是我们需要自己实现Context,在下面代码中我们首先在before函数中实现了Context 上下文,然后在rpcClient中开启消息事务,并调用 Cat.logRemoteCallClient(context) 去填充Context的这3个MessageID。当然,该函数还记录了一个RemoteCall类型的Event消息。
随后我们用rpcService函数中开启新线程模拟远程RPC服务,并将context上传到 RPC 服务器,在真实环境中,Context是需要跨进程网络传输,因此需要实现序列化接口。
在rpcService中,我们会调用 Cat.logRemoteCallServer(context) 将从rpcClient传过来的Context设置到自己的 Transaction 当中。
随着业务处理逻辑的结束, rpcServer 和 rpcClient 都会分别将自己的消息树上传到CAT服务器分析。
需要注意的是,Service的client和app需要和Call的server以及app对应上,要不然图表是分析不出东西的!
@RunWith(JUnit4.class)
public class AppSimulator extends CatTestCase {
public Map<String, String> maps = new HashMap<String, String>();
public Cat.Context context;
@Before
public void before() {
context = new Cat.Context() {
@Override
public void addProperty(String key, String value) { maps.put(key, value); }
@Override
public String getProperty(String key) { return maps.get(key); }
};
}
@Test
public void simulateHierarchyTransaction() throws Exception {
...
//RPC调用开始
rpcClient();
rpcClient2();
...
}
protected void rpcClient() {
//客户端埋点,Domain为RpcClient,调用服务端提供的Echo服务
Transaction parent = Cat.newTransaction("Call", "CallServiceEcho");
Cat.getManager().getThreadLocalMessageTree().setDomain("RpcClient");
Cat.logEvent("Call.server","localhost");
Cat.logEvent("Call.app","RpcService");
Cat.logEvent("Call.port","8888");
Cat.logRemoteCallClient(context, "RpcClient");
//开启新线程模拟远程RPC服务,将context上传到 RPC 服务器
rpcService(context);
parent.complete();
}
protected void rpcClient2() {
...
//模拟另外一个RpcClient调用Echo服务
rpcService(context, "RpcClient2");
...
}
protected void rpcService(final Cat.Context context, final String clientDomain) {
Thread thread = new Thread() {
@Override
public void run() {
//服务器埋点,Domain为 RpcService 提供Echo服务
Transaction child = Cat.newTransaction("Service", "Echo");
Cat.getManager().getThreadLocalMessageTree().setDomain("RpcService");
Cat.logEvent("Service.client", localhost); //填客户端地址
Cat.logEvent("Service.app", clientDomain);
Cat.logRemoteCallServer(context);
//to do your business
child.complete();
}
};
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
}
}
}
接下来我们看看CAT服务器端CrossAnalyzer的逻辑。
我们依然会为每个周期时间内的每个Domain创建一张报表(CrossReport),然后不同的IP会分配不同的Local对象统计,每个IP又可能会接收来自不同Remote端的调用。
由于这里一个完整的调用链会涉及多个端的多个消息树,我们首先会根据Transaction的类型来判断是RpcService还是RpcClient,如果Type等于PigeonService或Service则该消息来自RpcService,如果Type等于 PigeonCall或Call则来自RpcClient。
先来看看RpcService端消息树的上报处理逻辑,CAT会调用 parsePigeonServerTransaction 函数去填充 CrossInfo 信息,CrossInfo包含的具体内容如下:
localAddress : RpcService的IP地址
remoteAddress : 服务调用者(RpcClient)的IP地址,由type="Service.client" 的Event子消息提供,注意,在处理RpcClient的上报时,我们会根据上报信息中的remoteAddress再次统计该RpcService数据,大家可能会疑惑这里是不是重复统计,事实上他们所处的视角是不一样的,前者是站在服务提供者的视角来统计我完成这次服务所耗费的时间、资源等,而后者则是站在RpcClient视角去统计自己从发出请求到得到结果所需的时长、资源等等,比如这中间就包含网络IO的消耗,这些在后续的报表中会有体现。
app:客户端的Domain, 由type="Service.app"的Event子消息提供。
remoteRole:固定为 Pigeon.Client , 表示远端角色为 Rpc 客户端。
detailType: 固定为 PigeonService , 表示自己角色为 Rpc 服务端。
最后,我们将用CrossInfo信息来更新报表(CrossReport),我们首先根据 localAddress 即 RpcService的 IP 找到或创建 Local对象,然后根据 remoteAddress+remoteRole 找到或创建 Remote 对象,然后统计服务的访问量,错误量,处理时间,QPS。
RpcService提供不只一个服务,不同的服务我们按名字分别统计在不同的Name对象里,比如上面案例,RpcService提供的是Echo服务。
我们再来看看RpcClient端上报处理逻辑,CAT调用parsePigeonClientTransaction函数填充CrossInfo信息,具体如下:
localAddress : RpcClient的IP地址
remoteAddress :服务提供者(RpcService)的地址,由 type="Call.server" 的Event子消息提供。
app:服务提供者的Domain,由type="Call.app" 的Event子消息提供,在统计完RpcClient端数据之后,会通过该属性获取服务提供者的CrossInfo。从RpcClient的视角再次统计RpcService的数据。
port:客户端端口,由 type="Call.port" 的Event子消息提供。
remoteRole:固定为 Pigeon.Server, 表示远端角色为服务提供者。
detailType: 固定为 PigeonCall , 表示自己角色为服务调用者。
然后,我们将用CrossInfo信息来更新报表(CrossReport),也是根据 localAddress 找到Local对象,然后根据 remoteAddress+remoteRole 找到 Remote 对象,进行统计。
接着,我们通过convertCrossInfo函数利用RpcClient的CrossInfo信息去生成服务提供者的CrossInfo信息,这里实际上是为了从RpcClient的视角去统计服务提供者的报表!
public class CrossAnalyzer extends AbstractMessageAnalyzer<CrossReport> implements LogEnabled {
private void processTransaction(CrossReport report, MessageTree tree, Transaction t) {
CrossInfo crossInfo = parseCorssTransaction(t, tree);
if (crossInfo != null &&