目录
一、简介
二、思路
基本概念
traceId
rpcId
主体思路
线程安全
层级细节处理
入口层级处理
线程根层级处理
跨线程层级处理
三、总结
四、通用链路跟踪示例代码
分布式系统由于分别部署在不同的服务器上,服务之间的调用关系相比单体应用来说不是显而易见,另外各个服务之间的响应时间也是优化,排故的重要信息。因此,需要一套链路跟踪机制,springCloud基于zipkin提供了一套链路跟踪功能,但是我们想更灵活,可以加入自己需要的信息,新增跟踪点,控制细粒度。这样的话,我们可以自己实现一套链路跟踪功能。准备通过如下七篇文章来说说怎么实现自己的链路跟踪器。另外springCloud提供的zipkin的链路跟踪可以查看这篇文章《spring-cloud 分布式日志采集》
《springCloud微服务系列——链路跟踪第一篇——设计思路以及通用链路跟踪器》
《springCloud微服务系列——链路跟踪第二篇——mvc链路跟踪器》
《springCloud微服务系列——链路跟踪第三篇——feign链路跟踪器》
《springCloud微服务系列——链路跟踪第四篇——hystrix链路跟踪器》
《springCloud微服务系列——链路跟踪第五篇——mybatis链路跟踪器》
《springCloud微服务系列——链路跟踪第六篇——redis缓存链路跟踪器》
我们先看一下基本概念
traceId是一次链路跟踪的唯一标识
rpcId表示调用层级关系,类似于1.1.2这种形式
先说一下存储,存储还是存在Elasticsarch中
核心思路:
我们可以看到,整个思路看上起和堆栈类似,先进后出。
整个链路跟踪的时候我们会储存当前链路信息,为了跨实例获得信息,我们会存储在静态变量中。另外,有些操作也会产生新线程,比如hystrix熔断,很显然我们需要考虑线程安全的问题。我们可以通过ThreadLocal来进行保存。
上面的主题思路还算简单,但是实际上还有很多细节需要考虑,主要是在层级处理这一块。
入口层级就是请求第一次被处理的地方,也就是rpcId为1的地方。这个地方有几个特殊点
1、我们一般层级的获得方式为rpcId.split("[.]").length-1的方式获得,在这个地方不能通过该方式,只能指明为常量1
2、通过降级回到入口层级时,我们不能再用1来表示rpcId了,我们需要区别对待,因为层级加深的逻辑不同
如果rpcId为1,表示第一次加深入口层,我们需要将1变为1.1
如果rpcId为1.0,表示通过降级回到入口层,第n次加深入口层,我们需要将1变为1.(n+1)
3、该层级处理完毕后代表这次的整个链路跟踪结束,因此需要清空所有上下文信息
线程根层级出现在以下情况,调用其他服务时,mvc调用的时候。hystrix熔断调用的时候。因为这个时候启动了新的线程,这个时候传入的rpcId类似于该新线程的入口层级rpcId。这种情况有一个地方需要特殊处理,该层级处理完毕后代表该线程的链路跟踪结束,因此需要清空该线程的上下文信息
同上面说的调用其他服务时,hystrix熔断时,会产生新的线程,这个时候就不可能用之前线程的ThreadLocal获取到链路信息,需要通过其它方式传递过去,比如http,然后再把该rpcId作为线程更层级存到该线程的ThreadLocal中。
/**
* Title: GenericTracker.java
* Description:
* Copyright: Copyright (c) 2018-2099
* Company:
* @author wulinfeng
* @date 2018年7月24日上午11:41:23
*/
package com.luminary.component.trace.tracker;
import java.util.Calendar;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.lang3.time.FastDateFormat;
import org.slf4j.MDC;
import com.google.gson.Gson;
import com.luminary.component.trace.client.TraceClient;
import com.luminary.component.trace.model.RpcTraceInfoVO;
import com.luminary.component.trace.model.RpcTypeEnum;
import com.luminary.component.trace.model.TraceInfo;
import com.luminary.component.trace.thread.TraceContext;
import com.luminary.component.trace.tracker.GenericTracker.TraceHolder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
/**
* Title: GenericTracker
* Description: 基础的链路跟踪器
* @author wulinfeng
* @date 2018年7月24日上午11:41:23
*/
@Slf4j
public class GenericTracker implements Tracker {
private static final FastDateFormat ISO_DATETIME_TIME_ZONE_FORMAT_WITH_MILLIS = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ");
protected TraceClient traceClient;
public GenericTracker () {
}
public GenericTracker (TraceClient traceClient) {
this.traceClient = traceClient;
}
@Override
public void preHandle(TraceHolder holder) {
TraceInfo traceInfo = TraceContext.getTraceInfo();
if(traceInfo == null) {
traceInfo = new TraceInfo();
} else {
int level = 1;
if(TraceInfo.ORIGINAL_ROOT_RPC_ID.equals(traceInfo.getRpcId())) {
// 第一次入栈的处理逻辑,调用的总入口,此时rpcId为1
traceInfo.addHierarchy();
int maxSequenceNo = traceInfo.getHierarchyMaxSeqNo(level);
traceInfo.setSequenceNo(new AtomicInteger(maxSequenceNo+1));
}
else if(TraceInfo.RE_ORIGINAL_ROOT_RPC_ID.equals(traceInfo.getRpcId())) {
// 出栈到第一层的处理逻辑,traceInfo.subHierarchy()后,rpcId变为1.0的时候
int maxSequenceNo = traceInfo.getHierarchyMaxSeqNo(level);
traceInfo.setSequenceNo(new AtomicInteger(maxSequenceNo+1));
}
else if(traceInfo.getRootRpcId().equals(traceInfo.getRpcId())) {
// 跨线程后第一次入栈的处理逻辑,比如通过http请求到另一个服务的mvc,或者hystrix新创建一个线程
traceInfo.addHierarchy();
level = traceInfo.getRpcId().split("[.]").length-1;
int maxSequenceNo = traceInfo.getHierarchyMaxSeqNo(level);
traceInfo.setSequenceNo(new AtomicInteger(maxSequenceNo+1));
}
else {
// 普通情况的处理逻辑,一般大部分情况都是这个逻辑
traceInfo.addHierarchy();
level = traceInfo.getRpcId().split("[.]").length-1;
int maxSequenceNo = traceInfo.getHierarchyMaxSeqNo(level);
traceInfo.setSequenceNo(new AtomicInteger(maxSequenceNo+1));
}
}
// 允许通过holder传递traceId和rpcId,比如基于hystrix的tracker实现
if(holder.getTraceId() != null)
traceInfo.setTraceId(holder.getTraceId());
if(holder.getRpcId() != null)
traceInfo.setRpcId(holder.getRpcId());
traceInfo.cache();
TraceContext.putTraceInfo(traceInfo);
MDC.put(TraceInfo.TRACE_ID_KEY, traceInfo.getTraceId());
MDC.put(TraceInfo.RPC_ID_KEY, traceInfo.getRpcId());
RpcTraceInfoVO rpcTraceInfoVO = new RpcTraceInfoVO();
rpcTraceInfoVO.setProfile(holder.getProfile());
rpcTraceInfoVO.setRequestDateTime(ISO_DATETIME_TIME_ZONE_FORMAT_WITH_MILLIS.format(Calendar.getInstance().getTime()));
rpcTraceInfoVO.setTraceId(traceInfo.getTraceId());
rpcTraceInfoVO.setRpcId(traceInfo.getRpcId());
rpcTraceInfoVO.setRpcType(holder.getRpcType());
rpcTraceInfoVO.setServiceCategory(holder.getServiceCategory());
rpcTraceInfoVO.setServiceName(holder.getServiceName());
rpcTraceInfoVO.setMethodName(holder.getMethodName());
rpcTraceInfoVO.setRequestParam(holder.getRequestParam());
rpcTraceInfoVO.setServiceHost(holder.getServiceHost());
rpcTraceInfoVO.setClientHost(holder.getClientHost());
holder.setEntity(rpcTraceInfoVO);
holder.setStartTime(System.currentTimeMillis());
};
@Override
public void postHandle(TraceHolder holder) {
try {
RpcTraceInfoVO rpcTraceInfoVO = holder.getEntity();
if(rpcTraceInfoVO != null) {
String traceId = rpcTraceInfoVO.getTraceId();
if(traceId == null)
return;
rpcTraceInfoVO.setRunTime(System.currentTimeMillis() - holder.getStartTime());
rpcTraceInfoVO.setResult(RpcTraceInfoVO.RESULT_SUCCESS);
Gson gson = new Gson();
log.debug(gson.toJson(rpcTraceInfoVO));
traceClient.sendTraceInfo(rpcTraceInfoVO);
TraceInfo traceInfo = TraceContext.getTraceInfo();
traceInfo.subHierarchy();
TraceContext.putTraceInfo(traceInfo);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
};
@Override
public void exceptionHandle(TraceHolder holder, Exception ex) {
try {
RpcTraceInfoVO rpcTraceInfoVO = holder.getEntity();
if(rpcTraceInfoVO != null) {
rpcTraceInfoVO.setResult(RpcTraceInfoVO.RESULT_FAILURE);
rpcTraceInfoVO.setResponseInfo(ex.getMessage());
Gson gson = new Gson();
log.debug(gson.toJson(rpcTraceInfoVO));
traceClient.sendTraceInfo(rpcTraceInfoVO);
TraceInfo traceInfo = TraceContext.getTraceInfo();
traceInfo.subHierarchy();
TraceContext.putTraceInfo(traceInfo);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
@Data
public static class TraceHolder {
private long startTime;
private String traceId;
private String rpcId;
private String rpcType;
private String profile;
private String serviceCategory;
private String serviceName;
private String methodName;
private String requestParam;
private String serviceHost;
private String clientHost;
private RpcTraceInfoVO entity;
}
}
https://github.com/wulinfeng2/luminary-component