Java中的纳秒
前言
最近在使用InfluxDB保存系统的操作日志,如果在插入的时候不指定time字段,influxDB会默认设置time,但一般来说,数据经过多方周转如果采用默认的插入时间,对业务来说误差较高。我们的日志是发送给kafka,然后消费者订阅kafka消费数据再保存到influxDB,数据会有延迟。因此会在数据产生的时候写入time字段值,对时间的精度要求比较高,这样才能有效防止数据丢失。
Java中时间精度最高的应该是纳秒了,所以查询了下获取纳秒的一些方法。
JDK8获取纳秒的问题
JDK8中提供了丰富的时间的工具类,但是实际上JDK8中没有真正获取纳秒的方法,可查看这篇文章,https://stackoverflow.com/questions/1712205/current-time-in-microseconds-in-java,
Instant
类中可以获取纳秒的方法,本质上是获取的毫秒,后6位都是0。
Instant instant = Instant.now();
int tt = instant.getNano();
System.out.println(tt);
//返回的时间为576000000,后6位都是0>
查看java.time.Clock
类的源码可发现,本质是获取当前的System.currentTimeMillis()
生成时间类的。
static final class SystemClock extends Clock implements Serializable {
private static final long serialVersionUID = 6740630888130243051L;
private final ZoneId zone;
SystemClock(ZoneId zone) {
this.zone = zone;
}
@Override
public ZoneId getZone() {
return zone;
}
@Override
public Clock withZone(ZoneId zone) {
if (zone.equals(this.zone)) { // intentional NPE
return this;
}
return new SystemClock(zone);
}
@Override
public long millis() {
return System.currentTimeMillis();
}
@Override
public Instant instant() {
return Instant.ofEpochMilli(millis());//本质是获取当前的毫秒时间戳生成时间类的
}
@Override
public boolean equals(Object obj) {
if (obj instanceof SystemClock) {
return zone.equals(((SystemClock) obj).zone);
}
return false;
}
@Override
public int hashCode() {
return zone.hashCode() + 1;
}
@Override
public String toString() {
return "SystemClock[" + zone + "]";
}
}
Jdk还有一个方法System.nanoTime()
可以提供纳秒的精度测量的方法,一般可以用来计算2个方法之间的差值,不能直接转成时间戳。
long startTime = System.nanoTime();
// ... the code being measured ...
long elapsedNanos = System.nanoTime() - startTime;
//To compare elapsed time against a timeout, use
可以利用这个特性,初始化t1,获取差值从而获取纳秒。
t1=System.nanoTime(),
offset=System.currentTimeMillis()*1_000_000 - t1,
//获取当前的纳秒
currentNano=System.nanoTime() + offset
https://github.com/jenetics/jenetics/blob/master/jenetics/src/main/java/io/jenetics/util/NanoClock.java 就是利用这个方法实现的。
JDK9之后获取纳秒的问题
之前有提过这方面的缺陷,https://bugs.openjdk.java.net/browse/JDK-8068730,JDK9之后优化了获取纳秒的问题。
Instant instant = Instant.now();
int tt = instant.getNano();
System.out.println(tt);
//返回的时间为72765400,已经包含了纳秒。
java.time.Clock源码里,增加了VM.getNanoTimeAdjustment
方法获取纳秒。
public Instant instant() {
// Take a local copy of offset. offset can be updated concurrently
// by other threads (even if we haven't made it volatile) so we will
// work with a local copy.
long localOffset = offset;
long adjustment = VM.getNanoTimeAdjustment(localOffset);
if (adjustment == -1) {
// -1 is a sentinel value returned by VM.getNanoTimeAdjustment
// when the offset it is given is too far off the current UTC
// time. In principle, this should not happen unless the
// JVM has run for more than ~136 years (not likely) or
// someone is fiddling with the system time, or the offset is
// by chance at 1ns in the future (very unlikely).
// We can easily recover from all these conditions by bringing
// back the offset in range and retry.
// bring back the offset in range. We use -1024 to make
// it more unlikely to hit the 1ns in the future condition.
localOffset = System.currentTimeMillis()/1000 - 1024;
// retry
adjustment = VM.getNanoTimeAdjustment(localOffset);
if (adjustment == -1) {
// Should not happen: we just recomputed a new offset.
// It should have fixed the issue.
throw new InternalError("Offset " + localOffset + " is not in range");
} else {
// OK - recovery succeeded. Update the offset for the
// next call...
offset = localOffset;
}
}
return Instant.ofEpochSecond(localOffset, adjustment);
}
这时候如要获取纳秒的时间戳就简单了。
Instant instant = Instant.now();
int tt = instant.getNano(); // Represent a moment in UTC.
long time = instant.toEpochMilli()/1000*1000_000_000 + tt;
System.out.println(time);