背景
时间跳变导致定时任务失效
时间跳变的原因
- 网络原因:服务器长时间没有和ntp server进行时间同步,导致有偏差
- ntp服务器配置的ip有问题
- 服务器的系统时间被人为篡改: 比如date -s
故障检测
- 时钟跳变后会在/var/log/message有日志, 人工手动执行ntpdate恢复后也会有这条日志
7:27:36 VM-24-3-centos systemd: Time has been changed
故障恢复
- 查看ntp服务器ip: ntpstat可以查看有上层ntp连通的基本信息
ntpdate 169.254.0.83
^@^@ 8 Jan 17:33:17 ntpdate[28930]: adjust time server 169.254.0.83 offset -0.000775 sec`
- 手动同步一次时间: ntpdate {ntp server ip: 步骤1获取}
ntp时间同步原理
- ntp只会同步时间到本地的系统时间,硬件时间不会同步,需要用户打开开关
- ntp服务器同步时间时,只会告诉你utc时间, 然后本地接受到以后,根据自己配置的时区进行转换
- ntp服务器是层型结构,有顶端的服务器,多层的relay server, 再到客户端
- 能作为ntp server的机器,是通过原子钟进行测量时间,十分的昂贵。 因此会有很多relay server进行中转,来减少费用
编程时如何规避时间跳变
1: golang的ticket定时任务不受时间跳变影响
- 调用系统调用 clock_gettime 获取时钟值(这是 POSIX 时钟)。其中 clockid_t 时钟类型是 CLOCK_MONOTONIC,也就是不可设定的恒定态时钟
- 恒定态时钟的解释:不可设定的恒定态时钟。 Linux上测量时间始于系统启动,系统启动后就不会发生改变,适用于那些无法容忍系统时钟发生跳跃性变化的应用程序
- golang测试代码:
starttime := time.Now()
tolerance := 5
cnt := 0
for {
<-ticker.C
cnt++
interval := int(time.Since(starttime).Seconds())
//[20*cnt-tolerance, 20*cnt+tolerance]
if !(interval >= 10*cnt - tolerance && interval <= 10*cnt+tolerance) {
fmt.Printf("accour time changed. interval:%v, cnt:%v\n", interval, cnt)
}else {
fmt.Printf("normal. interval:%v, cnt:%v\n", interval, cnt)
}
interval2 := (time.Now().UnixNano() - starttime.UnixNano()) / int64(time.Second)
fmt.Printf("cur:%v, interval2:%v\n", time.Now().String(), interval2)
}
结论:
- golang的time.since(starttime): 得到的时间间隔是准确的, 时间跳变不会影响到这个值---原理:底层使用了单调时钟
- endtime-starttime: 这种写法得到的间隔是会受时间跳变影响, 可以用到检测时间跳变
2: 计算作业运行时间: endtime-starttime可能会变为负数
使用time.Since()方法可以获取到真实的时间间隔,这种方法不受时间跳变影响
3: 雪花算法的规避手段
- 延迟等待, 知道当前时间 < 之前的时间: 对于延时小的服务无法接受: 可以等待2秒,超过2秒就抛出异常
- 时间戳+自增序号: 如果当前的时间<之前的时间:那么继续使用之前的时间,只不过要在时间的基础上 改变自增序号来确定唯一值,---有个问题:自增序号用完之后时间跳变还没有恢复