上周三上午十点二十,客户群里炸了锅:"结算怎么现在才跑?!"他们的日结任务,一直标榜凌晨两点执行,那天上午十点突然动了,还把数据库 CPU 干到 92%,线上下单接口跟着卡成幻灯片。我远程登上他们的 K8s 集群翻调度日志,那里面白纸黑字写着"02:00 成功触发"。一边是日志里的凌晨两点,一边是监控里的上午十点,同一个任务,两个时间,差了整整八小时。说实话,那一刻我心里挺平静的——部署干了这么多年,我太熟悉这种"时间会对不上"的案子了。
(先岔一句闲话。我现在看任何系统日志,都有个改不掉的强迫症:先瞄一眼时间戳后面带没带时区标识。没带时区的时间戳,在我眼里跟没写时间差不多——你根本不知道那是哪个"平行世界"的表。这个习惯看着多余,实际救过我不止一次。)
谜底揭开的时候,简单到让人想笑。他们上个月刚做完容器化迁移,把老物理机上的定时任务搬进了 K8s,用的是 CronJob(K8s 里的定时任务资源)。老的 crontab(类 Unix 系统的定时任务配置)按北京时间写,搬过去的人觉得照抄就行,却不知道 K8s 调度器默认按 UTC(Coordinated Universal Time,协调世界时)算时间——YAML 里压根没写 timeZone 字段。于是"凌晨两点"被调度器理解成了 UTC 的两点,换算成北京时间,正好上午十点。八小时,就是这个差。
更让我头疼的是日志。容器里的应用默认也用 UTC 打日志,所以应用日志显示 02:00,调度记录显示 02:00,两边"完美对齐",唯独跟客户按北京时间配的监控对不上。排查的人盯着三套各自"自洽"的时间,怎么都拼不出真相。跑错八小时本身已经够呛——任务一头撞上早高峰,结算的批量查询跟真实用户流量挤在同一个数据库上,CPU 才被顶到 92%。要是只错一小时,可能到现在都没人发现。
改完 timeZone、重新调度,我本以为这事就结了。结果不到一周,同一个客户又找上门:他们开放给合作方的接口,开始间歇性报"签名过期"。校验逻辑不复杂,请求里带时间戳,服务器判断是否落在五分钟窗口内。我抓包看了半天,时间戳明明是新鲜的,还是报过期。折腾了一下午,答案在他们一台老物理机上:那台机器压根没开时间同步,系统时钟是它自己"自由发挥"的。装系统到现在快两年,没人管过它,它就慢慢飘——飘出了四十七秒。别小看这四十七秒,合作方的钟是准的,它快了将近一分钟,五分钟的窗口被硬生生挤掉一截,赶上网络再抖一抖,签名就"过期"了。
(对了,还有个细节我忍到今天才说。当时客户运维嫌我们排查慢,自己动手把那台机器的时区软链接改了,改完跟我说"应该好了吧,你们重启下应用就行"。可改了时区、没重启进程的 Java 应用,根本不认新时区——新写的日志还是老时区,跟之前的日志混在同一个文件里,一半一个时间基准,排查难度直接翻倍。你以为你修好了,其实你只是把水搅得更浑了。)
这两件事赶在一起,让我彻底把"时间"当成了正经的治理对象。后来我给团队立了几条规矩:容器镜像里显式设时区,不靠默认值;定时任务在声明里写死 timeZone;日志时间戳强制带时区偏移;每台机器的时间同步状态进监控,漂移超阈值就告警。尤其防"四十七秒"那种温水煮青蛙的那条,最容易被忽视——NTP(Network Time Protocol,网络时间协议)配好的机器,误差能压在毫秒级,可没人盯着的话,你根本不知道它哪天悄悄失联了。这些活儿没一件有技术含量,但它们决定你半夜排查时,看到的是真相还是幻觉。
我得诚实说,时间治理不是配一次就完的事。隔离在内网的客户,连不了公网时钟源,你得帮人家自建;有些精简容器镜像连 tzdata(时区数据库)都没装,你得往里塞;更极端的时间回拨,能让所有拿时间戳做幂等判重的逻辑一夜之间产出重复数据。而且跟客户提"加监控防时钟漂移",十个里有八个觉得我小题大做——直到他们自己也踩一次。
说句实在的,谁该把这套东西当回事?但凡你的系统里有定时任务、有跨机器查日志的需求、有结算签名有效期这类跟时间挂钩的逻辑,时区和时钟同步就必须管起来,别赌运气。谁不太需要?如果你就一个单机小服务,机器在国内、时区从没被动过,感知确实不强——但"日志带时区标识"这个零成本的习惯,我建议你现在就养起来。
那天收尾的时候,客户的运维负责人感慨了一句:"我干了十年运维,头一回有人跟我说,先看看服务器的手表准不准。"是啊,代码里最深的 bug,往往不在代码里,藏在这些我们都默认"它就该是对的"的地方。你不妨想想:你们生产机器的时区,你敢保证每台都一样吗?上一次有人校对时钟,又是什么时候?
下一篇,我想聊聊另一个被所有人默认"它不会出事"的东西——HTTPS 证书。我见过它在凌晨无人知晓的时候悄悄过期,也见过一支团队为此赔进去一整夜的睡眠。咱们下篇见。