spring cloud Sleuth 使用笔记
git 源码:https://github.com/spring-cloud/spring-cloud-sleuth
1.traceId 和spanId生成机制
类brave.tracer:
brave.internal.Platform
最终发现生成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传播下去。
借用官网上的图:
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
后续再更…