跳转至

离线单机的时间仲裁:三个时钟打架,账听谁的

云端系统里时间是基础设施给的:NTP 托管、时区统一,开发者很少意识到"现在几点"是一个问题。酒店这种离线单机场景把这个问题暴露得淋漓尽致:断电后 RTC 清零、前台电脑时区配错、时钟意外回退——而夜审过账、发卡、超时计费全部以时间为锚。这篇文章讲我们如何在没有权威时间源的环境里做时间仲裁。本文刻意停在"为什么必须仲裁、仲裁什么"这一层,不展开打分细节——对读者可复用的是框架,不是参数。

现象

三类症状,指向同一个根源:断电恢复后系统年份明显不合理;前台电脑时区配错时,房态图的"夜审迟了"提醒疯狂误报,而夜审真正卡住时反而恒定漏报;时钟意外回退时,发卡、夜审这类时间敏感操作面临基于错账时间执行的风险。

现场与复现记录分两路。误报一侧来自真实用户反馈(修复提交 ed97577 记载):部分本机时间或时区不对的电脑,房态图持续误报"夜审迟了";而本机正常、夜审真卡住的机器反而恒定漏报——因为前端拿服务端返回的"酒店时区今天"与客户端本地时钟比较,本机不对就误报,本机对了这个比较就永远相等。回退一侧的行为边界由守卫模块的单元测试钉住(本仓库 go test ./internal/mod/core/clockguard/ 实测通过):回退超过容忍度即冻结时间敏感操作、小幅回退被容忍(兼容对时的正常微调)、冻结态跨重启保持——重启洗不掉冻结。

我一开始的错误判断

两个典型的想当然,转折在提交链上看得清清楚楚:

其一,把"夜审迟了"误报当前端展示 bug 修——先做了一版前端真实时区换算(d05bd52),以为完事;用户继续反馈误报(ed97577 的修复记载),才意识到只要判定依赖客户端时钟,本机不对就误报、真卡住反而漏报,换算是治标。于是把判定整体搬到服务端:按业务日期与当日自动夜审时点计算,客户端降级为纯渲染。其二,把时钟回退当"重启就好"的环境小事,直到想明白它污染的不是显示,是账——回退期间落下去的每一笔时间戳都无法解释先后。

排查与关键证据

修复沿四个方向落子,构成仲裁体系的骨架(各自的行为边界,不涉及参数):

  1. 最后防线:只进不退的时钟戳。启动与周期刷新把"历史最大时钟"持久化,当前时钟回退超过容忍度即冻结时间敏感操作(发卡/夜审),并给出可操作的冻结原因;校准后解冻。容忍度本身是一个取舍——松了漏放真回退,紧了误伤对时的正常微调(具体数值见运维手册,文中不展开);
  2. 合理性守卫:只在变化沿发声。年份明显不合理的时钟(断电后 RTC 清零的典型特征)触发告警,但只在"离谱↔合理"的变化沿发事件,避免守卫本身变成告警轰炸源;
  3. 可信源:无网环境天然可信的那个。局域网内手机时间作为校准的可信源——离线场景里它是少数"大概率正确且无外网也可用"的源;校准动作要求管理员权限、全程审计留痕、校准后自动解冻;
  4. 判定收口:一切"迟没迟"只问服务端。"夜审迟了"的判定整体搬到服务端、按酒店时区计算,前端时钟降级为纯展示。误报与漏报同时消失——判定的正确性从此不依赖任何一台终端的健康度。

运维手册里有时钟异常的完整处置矩阵(自动行为与人工处置两张表),代码与手册双向回链——这是"仲裁规则要文档化"的落地形态。

根因

时间是计费与审计的锚。离线单机没有权威时间源,多个源(硬件时钟、网络对时、手机、持久化水印)必然出现分歧;分歧不裁决,错的就不是显示而是账。所以问题的正确表述不是"怎么把时间弄准",而是"多个时间源打架时,规则是什么、最后防线在哪、谁来执法"。至于各源的优先级如何取舍,属于实现纵深——本文的观点是:先有仲裁框架和最后防线,权重细节才有意义。

约束与取舍

  • 放弃了"信任 NTP 托管一切"——离线场景它缺席;即使在有网环境,它也只是仲裁体系里的一个源,不是执法者;
  • 放弃了静默自动校准——自动改时间无审计、出错不可追。代价是把"校准"升级成一个有权限、有留痕的人工动作;
  • 放弃了前端各自判定——多端时钟不可信,代价是服务端多承担一类判定逻辑;
  • 新增的代价:系统里多出一个"冻结态",需要解冻动作与运维手册配套;持久化时钟戳本身成为新的关键状态——它损坏时系统如何行为,必须显式设计而不是留给巧合。

可复用检查清单

  1. 上线前回答三问:有没有外网时间源?没有的话可信源是什么?都不对时的最后防线是什么——三问答不出任何一问,时间敏感业务就是在裸奔;
  2. 断电恢复后的第一检查项是时钟合理性,不是业务功能;
  3. 时间敏感操作(发卡/过账/计费)入口统一检查冻结态,不散落各处自行判断;
  4. 一切"迟到/超时/逾期"类判定一律放服务端、按业务时区计算,前端时钟只做展示;
  5. 校准时间是有审计价值的高危动作,必须留痕。

小结

离线系统的时间问题,本质不是校时问题,是仲裁问题。参数可以调,框架不能少:可信源、最后防线、判定收口、审计留痕,四件套缺一件,账迟早解释不了。