如何去除程序对系统时间的依赖
一、问题定义
定时调度、限流等业务场景中,都需要依赖时间来实现相关功能,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();