summary
2016 年末插入闰秒(2016-12-31 23:59:60 UTC)时,Cloudflare 的 RRDNS(Go 编写)在测量上游 DNS 解析器性能时,因时钟回拨产生负的平滑 RTT,该负值被传入 Go 的 rand.Int63n(),触发 panic,导致部分 CNAME 解析失败。
timeline
2017-01-01 00:00 UTC 影响开始;00:10 升级工程;00:34 确认根因;00:55 在 canary 节点部署缓解;随后逐步向各数据中心推送修复,06:45 UTC 全球影响结束。
root_cause
RRDNS 用 time.Now() 两次取值求差作为解析耗时,闰秒使时钟短暂回拨,差值变负并经过平滑后仍为负数;该负值喂给 rand.Int63n()(参数为负即 panic)。代码假设「时间不会倒流」,未按非单调时钟做负值保护。
amplifiers
① Go 的 time.Now() 不保证单调,闰秒回拨即暴露缺陷。② 负值经平滑累积才触发 panic,定位有延迟。③ 影响虽小(少数机器、<1% 请求、仅 CNAME 类),但跨 102 个数据中心。④ 修复需逐数据中心重启 RRDNS。
mitigation
修正 RRDNS:当时间差为负时丢弃该次测量并归一化,不让负值进入服务端选择代码;重启所有 RRDNS 清除已记录负值。后续审查所有时间差计算并采用闰秒 smearing。
lessons
任何用时钟差做计算的地方必须防御负值(时间可能倒流);关键路径避免使用会 panic 的随机函数处理外部输入;跨时区系统应统一采用闰秒 smearing。
checklist
① 全代码库排查时钟差负值处理。② 服务端选择/加权逻辑避免 panic 式函数处理外部输入。③ 采用闰秒 smearing 而非硬插入。④ 对 CNAME 解析错误率设分数据中心告警。