在 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
了。
参考
分析时钟实现