spring cloud Sleuth 使用笔记

spring cloud Sleuth 使用笔记
git 源码:https://github.com/spring-cloud/spring-cloud-sleuth
1.traceId 和spanId生成机制
类brave.tracer:
spring cloud Sleuth 使用笔记_第1张图片
spring cloud Sleuth 使用笔记_第2张图片 brave.internal.Platform
spring cloud Sleuth 使用笔记_第3张图片spring cloud Sleuth 使用笔记_第4张图片spring cloud Sleuth 使用笔记_第5张图片最终发现生成id机制是:
java.util.concurrent.ThreadLocalRandom.current().nextLong();
是一个long类型的非零随机数(64位二进制,-9223372036854775808 ~ 9223372036854775807之间的非零数值。)
向下游传播和MDC中都是被转成16位十六进制字符串(负正数以无符号处理,不足16位左边补0)。
关于ThreadLocalRandom,可以参考别人的博客《并发包中ThreadLocalRandom类原理剖析》:https://www.jianshu.com/p/9c2198586f9b
2.sleuth使用

 
      
          
              org.springframework.cloud
              spring-cloud-dependencies
              ${release.train.version}
              pom
              import
          
      


 
    org.springframework.cloud
    spring-cloud-starter-sleuth

Spring Cloud Sleuth 2.0.0以后添加了很多组件的支持,比如dubbo,apache httpclient,kafka,rabbitmq等,具体更新的内容有:
Rewritten using Brave #829, migration guide https://github.com/spring-cloud/spring-cloud-sleuth/wiki/Spring-Cloud-Sleuth-2.0-Migration-Guide
Removed the sleuth-stream #555 and zipkin-stream #727 dependencies. Spans via messaging can be only sent to Zipkin via native Zipkin dependencies.
spring.zipkin.sender.type=kafka needs to explicitly be set to send spans over Kafka #985, #1013
Added WebClient.Builder support #779
Trace tags account for parametrized URL #802
Added support for NettyClient instrumentation - allows instrumentation of Spring Cloud Gateway #806
Fixed all early bean initialization issues #870
Added spring-kafka support #896
Added spring-rabbitmq support #883
Added support for Apache HttpClient #685
Added OpenTracing support #599
Added support for AWS X-Ray #459
TraceKeys are hidden from the user and are deprecated #940
Added support for Dubbo #934
2.1具体每种组件的接入,参考官方文档:
https://cloud.spring.io/spring-cloud-static/spring-cloud-sleuth/2.1.0.RELEASE/single/spring-cloud-sleuth.html#_only_sleuth_log_correlation
2.2结合sef4j+logback框架打印traceId,spanId
logback.xml

....

            
                 
                    
                        {
                        "app_name": "dk-scm-task",
                        "timestamp": "%d{yyyy-MM-dd HH:mm:ss.SSS}",
                        "thread": "%thread",
                        "level": "%level",
                        "traceId": "%X{traceId}",
                        "spanId": "%X{spanId}",
                        "message": "%message"
                        }
                    
                  
            

.......

3 sleuth原理
3.1 http 传播机制
trace context 会被编码成 request headers传播下去。
借用官网上的图:
spring cloud Sleuth 使用笔记_第6张图片
3.2透传额外的字段
A difference from previous versions of Sleuth is that, with Brave, you must pass the list of baggage keys.
There are two properties to achieve this.
With the spring.sleuth.baggage-keys, you set keys that get prefixed with baggage- for HTTP calls and baggage_ for messaging.
You can also use the spring.sleuth.propagation-keys property to pass a list of prefixed keys that are whitelisted without any prefix. Notice that there’s no x- in front of the header keys.

@Configuration
@Order(TraceWebServletAutoConfiguration.TRACING_FILTER_ORDER - 2)
public class TraceIdAutoConfiguration {

    @Bean
    public SleuthProperties sleuthProperties() {

        String traceHeaderKey = "http_dk_trace_id";

        SleuthProperties sleuthProperties = new SleuthProperties();

        //会加baggage-前缀
        //        List dkBaggageKeys = new ArrayList<>();
//        dkBaggageKeys.add(traceHeaderKey);
//        List baggageKeys = sleuthProperties.getBaggageKeys();
//        if (baggageKeys == null || baggageKeys.size() == 0) {
//            sleuthProperties.setBaggageKeys(dkBaggageKeys);
//        } else if (!baggageKeys.contains(traceHeaderKey)) {
//            sleuthProperties.getBaggageKeys().add(traceHeaderKey);
//
//        }
        //不会加前缀
        List dkKeys = new ArrayList<>();
        dkKeys.add(traceHeaderKey);
        List propagaKeys=sleuthProperties.getPropagationKeys();
        if(CollectionUtils.isEmpty(propagaKeys)) {
          sleuthProperties.setPropagationKeys(dkKeys);
        }else if(!propagaKeys.contains(traceHeaderKey)) {
          sleuthProperties.getPropagationKeys().add(traceHeaderKey);
        }
        return sleuthProperties;
    }
}
@Component
@Order(TraceWebServletAutoConfiguration.TRACING_FILTER_ORDER + 1)
public class TraceIdFilter extends GenericFilterBean {

    private static final Logger LOGGER = LoggerFactory.getLogger(TraceIdFilter.class);

    private static final Log log = org.apache.commons.logging.LogFactory
            .getLog(GenericFilterBean.class);

    private final Tracer tracer;

    String traceHeaderKey = "http_dk_trace_id";

    TraceIdFilter(Tracer tracer) {

        this.tracer = tracer;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {

        try {
            String traceIdValue = ((HttpServletRequest) request).getHeader(traceHeaderKey);
            if(!StringUtils.isEmpty(traceIdValue)){
              MDC.put("traceId",traceIdValue);
            }
            
            if (StringUtils.isEmpty(traceIdValue)) {
                traceIdValue = tracer.currentSpan().context().traceIdString();
            }
            
            if (!StringUtils.isEmpty(traceIdValue) && StringUtils.isEmpty(ExtraFieldPropagation.get(traceHeaderKey))) {
                ExtraFieldPropagation.set(traceHeaderKey, traceIdValue);
            }
            

        } catch (Exception ex) {
            LOGGER.error("链路跟踪异常", ex);
        } finally {
            chain.doFilter(request, response);

        }

    }
}

3.3 php或者其他非springcloud sleuth项目对接
非springcloud sleuth 项目中http请求中header添加X-B3-TraceId,X-B3-SpanId即可,但是id生成机制要与sleuth一致,而且X-B3-SpanId必须带,否则后面的springcloud sleuth项目会重新生成新的traceId.

    public static String doGet(String url) {
       try {
            HttpClient client=TraceHttpClientUtil.getHttpClientProxy(new DefaultHttpClient());
            //发送get请求
            HttpGet request = new HttpGet(url);
            //生成策略
            Long traceId=ThreadLocalRandom.current().nextLong();
            Long spanId=ThreadLocalRandom.current().nextLong();
            //header传播时,需要转成十六进制hex字符串
            String traceIdStr=HexCodec.toLowerHex(traceId);
            String spanIdStr=HexCodec.toLowerHex(spanId);
            System.out.println("生成的traceId:"+traceIdStr+"\n生成的spanId:"+spanIdStr);
            //添加header
            request.addHeader("X-B3-TraceId", traceIdStr);
            request.addHeader("X-B3-SpanId",spanIdStr);
            //request.addHeader("X-B3-ParentSpanId","0");
            //request.addHeader("X-Span-Export","true");
            //request.addHeader("X-B3-Sampled","0");
            
            HttpResponse response = client.execute(request);
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                String strResult = EntityUtils.toString(response.getEntity());
                return strResult;
            }
        }catch (IOException e) {
            e.printStackTrace();
        }
        
        return null;
    }

从brave包里拷贝出来转十六进制的工具类

public final class HexCodec {

    /**
    * Parses a 1 to 32 character lower-hex string with no prefix into an unsigned long, tossing any
    * bits higher than 64.
    */
    public static long lowerHexToUnsignedLong(CharSequence lowerHex) {
     int length = lowerHex.length();
     if (length < 1 || length > 32) throw isntLowerHexLong(lowerHex);
    
     // trim off any high bits
     int beginIndex = length > 16 ? length - 16 : 0;
    
     return lowerHexToUnsignedLong(lowerHex, beginIndex);
    }
    
    /**
    * Parses a 16 character lower-hex string with no prefix into an unsigned long, starting at the
    * specified index.
    */
    public static long lowerHexToUnsignedLong(CharSequence lowerHex, int index) {
     int endIndex = Math.min(index + 16, lowerHex.length());
     long result = lenientLowerHexToUnsignedLong(lowerHex, index, endIndex);
     if (result == 0) throw isntLowerHexLong(lowerHex);
     return result;
    }
    
    /** Like {@link #lowerHexToUnsignedLong(CharSequence, int)}, but returns zero on invalid input */
    public static long lenientLowerHexToUnsignedLong(CharSequence lowerHex, int index, int endIndex) {
     long result = 0;
     while (index < endIndex) {
       char c = lowerHex.charAt(index++);
       result <<= 4;
       if (c >= '0' && c <= '9') {
         result |= c - '0';
       } else if (c >= 'a' && c <= 'f') {
         result |= c - 'a' + 10;
       } else {
         return 0;
       }
     }
     return result;
    }
    
    static NumberFormatException isntLowerHexLong(CharSequence lowerHex) {
     throw new NumberFormatException(
         lowerHex + " should be a 1 to 32 character lower-hex string with no prefix");
    }
    
    /** Inspired by {@code okio.Buffer.writeLong} */
    public static String toLowerHex(long v) {
     char[] data = new char[16];
     writeHexLong(data, 0, v);
     return new String(data);
    }
    
    /** Inspired by {@code okio.Buffer.writeLong} */
    public static void writeHexLong(char[] data, int pos, long v) {
     writeHexByte(data, pos + 0, (byte) ((v >>> 56L) & 0xff));
     writeHexByte(data, pos + 2, (byte) ((v >>> 48L) & 0xff));
     writeHexByte(data, pos + 4, (byte) ((v >>> 40L) & 0xff));
     writeHexByte(data, pos + 6, (byte) ((v >>> 32L) & 0xff));
     writeHexByte(data, pos + 8, (byte) ((v >>> 24L) & 0xff));
     writeHexByte(data, pos + 10, (byte) ((v >>> 16L) & 0xff));
     writeHexByte(data, pos + 12, (byte) ((v >>> 8L) & 0xff));
     writeHexByte(data, pos + 14, (byte) (v & 0xff));
    }
    
    static final char[] HEX_DIGITS =
       {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
    
    static void writeHexByte(char[] data, int pos, byte b) {
     data[pos + 0] = HEX_DIGITS[(b >> 4) & 0xf];
     data[pos + 1] = HEX_DIGITS[b & 0xf];
    }
    
    HexCodec() {
    }
}

需要注意的一点是,上游项目向后传播的header里的spanId是下游项目MDC中使用的spanId,下游项目再向后传播时会再生成新的spanId,用于下下游项目使用。
3.4 Brave源码分析-Tracing
参考文章:https://blog.csdn.net/apei830/article/details/78722209
后续再更…

你可能感兴趣的:(链路追踪)