工程上最危险的不是完全不可用,而是在理想环境里正常、进入真实网络和真实数据量后悄悄失去边界。 进程内耗时应由 monotonic clock 计算,因为它不随系统校时倒退;跨机器传递的是服务端签发的绝对截止时间和允许偏差,接收方重新计算本地预算。
分布式正确性来自幂等键、租约、单调版本和可对账事实,而不是假设请求只到一次。超时代表结果未知,调用方不能未经确认就把它解释成失败。
工程边界与取舍
这项能力横跨客户端、网络与服务端,局部最优很容易制造全局故障。工程决策需要同时约束两端行为、持久化事实和运维预算。
- API 类型区分 Instant、Duration 与 WallTimestamp,禁止把 Date.now 差值用于重试预算;休眠恢复后按剩余截止时间重新调度。
- 先定义正常、降级、取消和失败四类终态,再让界面、存储与指标消费同一状态。
- 兼容性通过显式能力协商决定,旧客户端得到可解释的降级路径,而不是进入半可用状态。
“超时为什么要用单调时钟:墙上时间会跳”的交付标准是正常路径可用、异常路径收敛、资源有界,并且用户能理解系统为何采用当前状态。 这样得到的不是演示代码,而是能解释、能降级、能回滚的生产能力。
生产环境会怎样出错
失败路径需要与成功路径共享同一套状态模型。只弹一个错误提示却不释放资源、不传播终态,下一次恢复就会继承脏状态。
- NTP 把时钟向后调时,基于墙钟的锁持有时间突然增加;向前跳则让所有租约在同一刻提前过期。
- 失败被压成一个布尔值,调用方无法区分可重试、需用户介入与永久拒绝,最终形成无限循环。
- 指标标签直接包含用户或任务标识,既制造高基数成本,也让诊断数据泄露不必要的身份信息。
把测试跑成闭环
测试不能以“最后成功了”结束。还要测量经历了多少次副作用、等待多久、是否泄露内容,以及失败后再次执行能否从干净基线开始。
- 在测试时钟中注入前跳、后跳、休眠两小时和时区改变,断言持续时间逻辑只受单调推进影响且跨服务截止时间保守收敛。
- 让刷新、取消、超时和远端完成在同一调度窗口发生,断言系统只产生一个终态和一次副作用。
- 发布前记录成功率、p50/p95/p99 延迟、错误分类和资源高水位,并为回退条件设定明确阈值。
当实现能够安全降级、重复执行不产生额外副作用、故障指标早于投诉出现时,这项能力才算具备长期维护价值。