最近Russ Cox提了一条proposal《Monotonic Elapsed Time Measurements in Go》
这个proposal主要解决了一个问题,计算时间差。
我一开始觉得,这不是很简单么?例如:
t1 := time.Now()# ... 10 ms of work t2 := time.Now() const f = "15:04:05.000" fmt.Println(t1.Format(f), t2.Sub(t1), t2.Format(f))
简单的相减,就可以得到时间差。但这里隐含了一个谬误,就是:
默认:程序后执行的Now,肯定比之前执行的Now晚。
时间,在人的认知里肯定是单向向前,但在计算机系统里,却不是。因为有正、负闰秒的存在,常常有bug出现,例如:Cloudflare RRDNS 故障。
这起事故中,CF的RRDNS使用两个时间的差值进行权重计算,平时是没有问题,但当闰秒的出现时,程序差值就出现了负数,最终酿成了事故。所以在对时间差有严重依赖的程序(纳入计算权重等),需要使用的是monotonic time(单调递增时间),而不是wall time(系统时间),这也就促成了Russ Cox的proposal。
不但如此,程序员还有对于时区、时差、时间精度、起始的谬误。因此,就有人总结出了《程序员认知时间的谬误(Falsehoods programmers believe about time)》 ,我摘抄了部分国内程序员常犯的错误,括号中是我个人对于这些谬误的理解:
- 一天总有24小时
- 一个月有30或者31天(还有二月份)
- 一年有365天 (闰秒导致多一秒,1582年整整消失了10天)
- 二月份总是28天(有29天)
- 24小时是一天、周、月的周期值(对时间取模算天数、我也犯过)
- 每年相同月份里,周的起始是相同的
- 机器的时区永远是GMT,不,开玩笑的,是机器的时区永远不变(时区的设置可能会被调整)
- 系统时钟永远设置的是本地对应的时区的时间
- 目标时区和GMT永远有相同的间隔时间(时区会变化,夏令时)
- 客户端的时间和服务器时间永远相同
- 客户端时间和服务器时间的差值没什么大不了的
- 就算CS有差值,总是相同的间隔(客户端可能会动态调整时间)
- 服务器和客户端的时间永远在同一个时区
- 系统时间不会在5000年前、或者5000年后
- 时间没有起点和尽头(千年虫、Unix epoch)
- 系统中的一分钟和其他机器上的一分钟应该是一样的(各个机器上CPU频率等问题导致时钟偏移,所以请使用ntp)
- 最小的时间单位是秒、呃、毫秒……(最小单位是CPU确定的)
- 大家都能明白时间戳格式(1339972628 或者是 133997262837)
- 时间戳格式永远是同一个格式(64位和32位就有区别)
- 时间戳精度永远相同(float精度问题)
- 时间戳的精度保证了可以做uid
- 全世界都能明白11/07/05是什么时间格式(年月日显示格式不明确)
我的经历中,见得比较多的是对于时区了解的匮乏,很多程序员并不知道夏令时,甚至有产品或者老板的需求就只是给国人服务,当要全球化时就抓瞎了。当然,最多的还是对闰秒的无视,或者有人根本不知道闰秒的存在。不过,这些bug都因为系统对时间依赖程度不高,或因为系统跑的时间不够长,(没长到碰到闰秒就下线了),所以并没有导致算错帐或者生产事故。不过,这些理由都不能为这种bug开脱,所以程序员要学习东西还是很多的。
p.s. 你在生产系统里碰到过什么关于时间的bug?欢迎讨论~