返回资讯中心
外部精选
软件工程
#开发者实时流

开发者常忽视时间假设,系列文章讨论如何构建更健壮系统

来源标题:Beyond Happy Path Engineering: Time

一篇技术文章指出,多数软件在理想条件下开发,却忽视生产环境中的网络延迟、数据库响应和用户行为等不确定性。该系列文章将逐一分析网络、时间、数据库、存储、用户、依赖、部署、队列和并发等环节中被默认的假设,探讨如何提升程序健壮性。

Hacker News 最新fagnerbrack2026/11/14 · 今天30 分钟阅读

以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。

收录于 TLDR Dev - 2026-07-22 和 Pointer #735

大多数软件最初都是在异常友好的条件下构建出来的。在开发者机器上,数据库就在旁边,网络很安静,你需要的服务正在运行,测试用户行为合理,时钟向前走,队列会被消费完,部署能完成,请求会成功。这些条件很有用,它们让我们能先把东西的第一版做出来,而不必操心生产环境里可能出问题的一切。请求并不总会成功,网络并不总是很快,数据库并不总会立即响应,部署并非瞬间完成,用户的行为不可预测,依赖项也有自己的限制、维护窗口、bug、过载和倒霉的日子。一个只为顺畅路径设计的系统,大多数时候可能运行得很好,但当现实偏离那条路径时,事情就会崩溃。

本系列讲的是如何让你的程序更有韧性。每篇文章聚焦于我们习以为常的系统中的某一个部分:网络、时间、数据库、存储、用户、依赖、部署、队列和并发。问题始终是同一个:我们在这里做了哪些假设,当这些假设不再成立时,我们该怎么办?

时间看起来是程序中最简单的依赖之一:你请求当前时刻,把它和另一个时刻比较,然后做出决策。这个决策可能是预订是否已过期、会话是否仍然有效、重试是否应该再等一会儿,或者某份报告是否属于昨天。代码通常用一个时间戳、一个时长,或者一次对 now() 的调用来处理它,答案看起来显而易见。

在生产环境中,答案并不像代码所暗示的那样显而易见。机器对当前时间的认识并不一致,墙钟校正可能在操作进行中把时钟向前或向后拨动。时长和日历日期是两回事,而时间戳并不能可靠地证明一个事件先于另一个事件发生。计划任务可能延迟运行、运行两次,或者在一阵爆发中追赶进度。面向用户的时间还承载着本地和业务含义——午夜、计费周期、营业时间以及夏令时规则,都取决于比一个统一时钟更多的东西。

本文把时间视为一个设计边界。正确的时间类型取决于所问的问题:衡量工作用经过的时长,记录事件用稳定的时刻,为竞争性写入排序用版本或事务,用户看到的任何东西则用时间时区和业务规则。一旦这些区分变得明确,时间就不再是一个隐藏的假设,而成为系统可以推理的对象。

考虑一个结账流程:在用户完成支付期间为商品保留库存。预留持续十五分钟。如果用户在这个窗口关闭前完成支付,订单就变为完成状态,预留不再需要过期。如果用户走开了,后台任务最终会释放库存,以便另一位顾客可以购买。

顺畅路径很容易画出来,因为每个决策对事件顺序的看法都一致:

在这个层面上,时间表现得像一个共享的事实。结账流程记录预留将在 10:15 过期。支付在 10:08 完成。订单在 10:09 被标记为完成。当过期任务稍后运行时,它看到预留已经变成了订单,于是不再处理它。对于哪个事件先发生没有分歧,机器时间和用户时间之间没有间隙,也没有延迟的 worker 试图在新的上下文中做出旧的决策。

这种模型自然会引出把时间当作简单比较的代码:

if now() > reservation.expiresAt: release(reservation)

孤立地看,这完全合理。它表达了程序员的意图,而且很容易测试。创建一条预留,把时钟拨过过期时间,验证商品被释放。大多数时候,生产环境也会足够友善,让这个心智模型看起来是正确的。

一旦系统增长到超出那条干净的时间线,隐藏的假设就开始浮现。代码假设 now() 在决策运行的任何地方含义都相同。它假设存储的时间戳仍然具有写入者所意图的含义。它假设过期任务在它所评判的事件之后运行,并且按时间戳排序记录所讲述的故事与业务流程一致。顺畅路径仍然有价值,但它对自己需要周围的时钟、调度器、数据库和 worker 提供多少一致性保持沉默。

这个模型的第一个裂缝在于:时间不是单一的工具。一个程序可能用相同的类型、相同的列或相同的词来问几个不同的问题:这花了多长时间,用户什么时候做了这件事,哪次更新应该胜出,这条预留是否仍然有效,这个事件属于哪个业务日?这些问题看起来相关,因为它们都涉及时间,但它们并不是同一个工程问题。

下图是本文其余部分将使用的心智划分:

墙钟时间是人看时钟或日历时通常所指的时间。它适合记录用户在某个特定时刻点击了按钮、在审计日志中显示时间戳,或者向客服解释订单何时从一种状态变为另一种状态。它也最可能承载人的期望,因为用户不以单调计数器思考;他们以天、小时、时区和预约来思考。

单调时间回答的是另一个问题:经过了多少时间?这是你用来测量延迟、执行超时、拉开重试间隔,或者判断一个请求是否已用完其有效等待预算的时钟。单调时钟不需要告诉你现在是 10:08;它需要持续向前走,以便系统能够测量时长而不被墙钟校正搞混。

(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →