springCloud微服务系列——链路跟踪第一篇——设计思路以及通用链路跟踪器

 

目录

 

一、简介

二、思路

基本概念

traceId

rpcId

主体思路

线程安全

层级细节处理

入口层级处理

线程根层级处理

跨线程层级处理

三、总结

四、通用链路跟踪示例代码


一、简介

  分布式系统由于分别部署在不同的服务器上,服务之间的调用关系相比单体应用来说不是显而易见,另外各个服务之间的响应时间也是优化,排故的重要信息。因此,需要一套链路跟踪机制,springCloud基于zipkin提供了一套链路跟踪功能,但是我们想更灵活,可以加入自己需要的信息,新增跟踪点,控制细粒度。这样的话,我们可以自己实现一套链路跟踪功能。准备通过如下七篇文章来说说怎么实现自己的链路跟踪器。另外springCloud提供的zipkin的链路跟踪可以查看这篇文章《spring-cloud 分布式日志采集》

《springCloud微服务系列——链路跟踪第一篇——设计思路以及通用链路跟踪器》

《springCloud微服务系列——链路跟踪第二篇——mvc链路跟踪器》

《springCloud微服务系列——链路跟踪第三篇——feign链路跟踪器》

《springCloud微服务系列——链路跟踪第四篇——hystrix链路跟踪器》

《springCloud微服务系列——链路跟踪第五篇——mybatis链路跟踪器》

《springCloud微服务系列——链路跟踪第六篇——redis缓存链路跟踪器》

二、思路

  我们先看一下基本概念

基本概念

  • traceId

   traceId是一次链路跟踪的唯一标识

  • rpcId

   rpcId表示调用层级关系,类似于1.1.2这种形式

主体思路

   先说一下存储,存储还是存在Elasticsarch中

   核心思路:

  •    请求开始的时候,生成一个traceId。这个时候rpcId为1。我们把该信息存储到线程安全的地方。
  •    每一个需要跟踪的地方被调用,从当前线程中获取链路跟踪信息,将层级加深,比如1.1
  •    层级加深的时候,我们需要判断是第一次进入该层次,比如加深后为1.1.1,还是第n次进入该层次,比如加深后为1.1.(n+1)。因此我们需要缓存每一层的最大序列号,加深的时候查一下下一层的最大序列号n,没有则为0,我们就可以获得加深后的序列号为(n+1)
  •    每次调用完毕将层级降级,比如从1.1.1降到1.1(你可能已经注意到,从1.1降级怎么办?是1还是1.0,我们用1.0,详细的 情况我们后面再说)

   我们可以看到,整个思路看上起和堆栈类似,先进后出。

线程安全

    整个链路跟踪的时候我们会储存当前链路信息,为了跨实例获得信息,我们会存储在静态变量中。另外,有些操作也会产生新线程,比如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中。

三、总结

四、通用链路跟踪示例代码

/**  
* <p>Title: GenericTracker.java</p>  
* <p>Description: </p>  
* <p>Copyright: Copyright (c) 2018-2099</p>  
* <p>Company: </p>  
* @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;

/**  
* <p>Title: GenericTracker</p>  
* <p>Description: 基础的链路跟踪器</p>  
* @author wulinfeng
* @date 2018年7月24日上午11:41:23
*/
@Slf4j
public class GenericTracker implements Tracker<TraceHolder> {
	
	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

猜你喜欢

转载自blog.csdn.net/guduyishuai/article/details/81357249