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中。

三、总结

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

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

/**  
* 

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

你可能感兴趣的:(spring-cloud)