在 Zipkin 的客户端实现中,Brave 使用了一种比较特别的方式。还记得我们上次在讲 PendingSpans
得时候留下的坑吗?
package brave.internal.recorder;
public final class PendingSpans extends ReferenceQueue<TraceContext> {
public PendingSpan getOrCreate(TraceContext context, boolean start) {
if (context == null) throw new NullPointerException("context == null");
reportOrphanedSpans();
PendingSpan result = delegate.get(context);
if (result != null) return result;
MutableSpan data = new MutableSpan();
if (context.shared()) data.setShared();
// 通常在创建一个新的Spand的时候,他的parentSpan应该会在执行状态
// 那么Brave为了节约计算时间的额外损耗,这里会首先获取这个context的父级
TickClock clock = getClockFromParent(context);
// 如果无法获取到父级,一般可能是这是一个新的Span,或者是父级的Span已经被回收了
if (clock == null) {
clock = new TickClock(this.clock.currentTimeMicroseconds(), System.nanoTime());
if (start) data.startTimestamp(clock.baseEpochMicros);
} else if (start) {
data.startTimestamp(clock.currentTimeMicroseconds());
}
PendingSpan newSpan = new PendingSpan(data, clock);
PendingSpan previousSpan = delegate.putIfAbsent(new RealKey(context, this), newSpan);
if (previousSpan != null) return previousSpan; // lost race
if (trackOrphans) {
newSpan.caller =
new Throwable("Thread " + Thread.currentThread().getName() + " allocated span here");
}
return newSpan;
}
}
这里的TickClock是很有讲究的,一般在JDK9以前,Java中是无法获取到微秒级别的绝对时间精度的。比如在JDK8中,通过System.nanoTime()
得到的时间只能用于计算相对时间,它的返回值并不能与任何真实时间挂钩。比如Oracle官方给出的案例,
long startTime = System.nanoTime();
// … the code being measured …
long estimatedTime = System.nanoTime() - startTime;
可以被用于计算相对时间,能够达到纳秒的精度。而这里Brave采用了一个非常巧妙的方式,从TickClock
中可以看到,
package brave.internal.recorder;
import brave.Clock;
final class TickClock implements Clock {
final long baseEpochMicros;
final long baseTickNanos;
TickClock(long baseEpochMicros, long baseTickNanos) {
// 基准绝对时间,单位us
this.baseEpochMicros = baseEpochMicros;
// 基准相对时间,单位ns
this.baseTickNanos = baseTickNanos;
}
@Override public long currentTimeMicroseconds() {
// 在计算当前时间的时候,会通过当前的nanoTime() - 基准相对时间,
// 再加上 基准绝对时间 就可以计算得到当前的时间,其中流逝的时间精度为纳秒
return ((System.nanoTime() - baseTickNanos) / 1000) + baseEpochMicros;
}
@Override public String toString() {
return "TickClock{"
+ "baseEpochMicros=" + baseEpochMicros + ", "
+ "baseTickNanos=" + baseTickNanos
+ "}";
}
}
由于这里流逝的时间精度为纳秒,可以很好的满足Span
中Duration
计时的时间精度。但这里baseEpochMicros
的时间精度则是取决于JDK的版本。
我们首先看一下Brave中Clock
这个接口,
package brave;
// FunctionalInterface except Java language level 6
public interface Clock {
// 是一个FunctionalInterface,只有一个方法
long currentTimeMicroseconds();
}
Brave
中把一些平台相关的功能都放到了Platform
这个类里面。
package brave.internal;
public abstract class Platform {
…
public Clock clock() {
return new Clock() {
// <= JDK8
@Override public long currentTimeMicroseconds() {
return System.currentTimeMillis() * 1000;
}
@Override public String toString() {
return "System.currentTimeMillis()";
}
};
}
static class Jre9 extends Jre7 {
@IgnoreJRERequirement @Override public Clock clock() {
// JDK9+
return new Clock() {
// we could use jdk.internal.misc.VM to do this more efficiently, but it is internal
@Override public long currentTimeMicroseconds() {
java.time.Instant instant = java.time.Clock.systemUTC().instant();
return (instant.getEpochSecond() * 1000000) + (instant.getNano() / 1000);
}
@Override public String toString() {
return "Clock.systemUTC().instant()";
}
};
}
@Override public String toString() {
return "Jre9{}";
}
}
}
- 默认JDK8及以下版本直接使用
System.currentTimeMillis() * 1000
,则精度为毫秒 - JDK9及以上版本使用
Instant
来计算得到微秒精度的时间。
The range of an instant requires the storage of a number larger than a long. To achieve this, the class stores a long representing epoch-seconds and an int representing nanosecond-of-second, which will always be between 0 and 999,999,999. The epoch-seconds are measured from the standard Java epoch of 1970-01-01T00:00:00Z where instants after the epoch have positive values, and earlier instants have negative values. For both the epoch-second and nanosecond parts, a larger value is always later on the time-line than a smaller value.
根据官方文档,Instant使用的是epoch-seconds
+nanosecond-of-second
来表示当前时间,可以达到纳秒的精度。如此也就可以理解上述的TickClock
了。