实战

收银台不在的时候 — 补上我们留着的一个洞

从创刊号起,这本杂志就一直写着「从未测过离线行为」。这次我们中途把收银进程杀掉,又接了三单。「不丢」和「不重复收」是相反方向的失败,所以两边都检查。

作者: makemind · 2026年8月14日

这本杂志六个月里反复写着同一句话。

离线与缓存行为 — 没有任何样例试过

(原样摘自回望 2026 年 1 月。)这一篇把那行删掉。

为什么它一直留着

不是懒。离线很难测。 测「有连接」的状态很容易 — 跑一下能用就行。测「没有」的状态就得先把它弄没,而「没有」有好几种。

这个样例只处理其中最糟的两种。

  1. 没地方可送。 而客人就站在你面前。
  2. 送了却没回音。 到没到,点单板无从知晓。

这两者导向相反方向的失败。不处理第一个,订单会消失;不处理第二个,会被收两次钱。加一个队列只解决第一个,第二个就冒出来了 — 这就是这一篇想说的。

收银台在的时候

TILL ONLINE —— 记录板收到的单子直接到达收银台。下面的队列是空的,今天 1 单 3.5k won
TILL ONLINE —— 记录板收到的单子直接到达收银台。下面的队列是空的,今天 1 单 3.5k won
till: connected
online: took 1, delivered 1, till has 1 (3.5k won)

平平无奇。按了一杯咖啡,收银台收下了。

收银台消失

till: process gone — the pad is now on its own

我们杀掉了进程。对餐车来说就是热点断了,而从点单板这一侧看没有区别 — 一样的只是「发出去的东西收不到回应」。

然后又接了三单。

TILL UNREACHABLE —— 记录板照收不误。WAITING TO REACH THE TILL 里排着三个 id:pad1-002 · pad1-003 · pad1-004
TILL UNREACHABLE —— 记录板照收不误。WAITING TO REACH THE TILL 里排着三个 id:pad1-002 · pad1-003 · pad1-004
offline: took pad1-002 (sandwich) — nothing was sent
offline: took pad1-003 (coffee) — nothing was sent
offline: took pad1-004 (juice) — nothing was sent
offline: pad still took 3 orders, 3 waiting, ids pad1-002 pad1-003 pad1-004

画面是红的,是为了诚实。 把「东西根本没发出去」藏起来,那不是离线支持,是离线遮掩。可以继续接单,但必须让人看得见还有几单没走。

发件箱就是全部答案

这个样例的答案是一个类。点单板从不直接调用收银台。

/// The order pad never calls the till directly. It writes into here, and here
/// tries to deliver. That one indirection is what lets the person behind the
/// counter keep working while the till is unreachable, and it is also what
/// makes the failure honest: the pad can show how many orders are still
/// undelivered, because the outbox knows.

这一层同时给了两样东西:能继续干活,以及能说出积压了几单。

规则只有两条。

① id 在这里生成,在第一次尝试之前。

final p = Pending('$device-${(++_minted).toString().padLeft(3, '0')}', item);

如果由收银台分配 id,重发就变成了新订单。那客人就被收两次钱。重试要安全,前提是「要重试什么」在重试之前就已定下。

② 按顺序发,第一次失败就停。

} catch (_) {
  // Still ours. Leave it at the front and stop — the ones behind it are
  // newer and must not overtake.
  break;
}

跳过去先发后面的,账本就把这个早上记错了。12 点的订单盖在 11 点的前面,那就是一本错的账。

而且队列在文件里。

/// Where the queue lives between app launches. A queue that only exists in
/// memory is not a queue — closing the lid is the same as losing the orders.
final File spoolFile;

此内容需要开发者或更高等级

登录并升级您的方案即可继续阅读。

查看方案
Twitter