如何去除程序对系统时间的依赖

一、问题定义

定时调度、限流等业务场景中,都需要依赖时间来实现相关功能,Java中通常的做法是通过System.currentTimeMillis()获取系统时间。

当系统时间出问题的时候,特别是NTP服务器不稳定,系统时间出现调变的时候,将严重影响相关功能的正确性和稳定性,甚至给业务带来灾难性的影响。

二、问题分析

如何去除程序对系统时间的依赖呢?

首先思考业务逻辑的本质,真的需要一个“绝对的”的时间点吗?还是只是需要时间间隔。
如果是需要记录订单生成时间、合同签订时间等,那没有办法,只好依赖NTP。
但更多的业务场景仅仅是需要记录时间间隔,如:计算处理时间、定时调度、限流。

JDK本身其实已经提供了解决方案。

三、问题解决

1、使用 System.nanoTime()替换System.currentTimeMillis()

System.currentTimeMillis()获得的是系统时间的绝对值。

而System.nanoTime()的关注的是多次调用之间的相对值,多次调用返回的值是“任意”设定的一个时间点,而且这个时间点不随着系统时间的变化而变化。

System.nanoTime()的这个特性刚好是解决上面问题的“银弹”。

需注意:System.nanoTime()返回值之间的比较需在同一个jvm实例中进行。

2、使用ScheduledExecutorService而不是Timer

Timer用的系统时间,在系统时间出问题的时候,调度任务的触发会有问题。

ScheduleExcutorService触发时间的计算基于System.nanoTime(),不会有这样的问题。

就这么简单。

四、其他

附赠一个dubbo限流bug的解决方案:

https://github.com/apache/incubator-dubbo/blob/f720ccb965d490e6cc328af8d3e9820cb6eaf8f7/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/tps/StatItem.java#L42-L46

由于限流代码中使用量System.currentTimeMillis(),使用了令牌桶算法,系统时间忽然变动到当前系统前的情况下,桶不能得到重置,导致正常访问被限流拒绝掉。

解决的办法很简单,使用 System.nanoTime()替换System.currentTimeMillis()即可。

五、附录

System.nanoTime() 方法注释:

/** * This method can only be used to measure elapsed time and is * not related to any other notion of system or wall-clock time. * The value returned represents nanoseconds since some fixed but * arbitrary origin time (perhaps in the future, so values * may be negative). The same origin is used by all invocations of * this method in an instance of a Java virtual machine; other * virtual machine instances are likely to use a different origin. * * This method provides nanosecond precision, but not necessarily * nanosecond resolution (that is, how frequently the value changes) * - no guarantees are made except that the resolution is at least as * good as that of {@link #currentTimeMillis()}. * * The values returned by this method become meaningful only when * the difference between two such values, obtained within the same * instance of a Java virtual machine, is computed. * * @return the current value of the running Java Virtual Machine's * high-resolution time source, in nanoseconds * @since 1.5 */ public static native long nanoTime();

你可能感兴趣的:(如何去除程序对系统时间的依赖)