> 预留不加租约,配额会单调归零——而且每一步决策都是对的,没有任何错误可查
任何"先扣一个估计值,等真实值到了再对账"的协议,都必须给那笔预留加租约。 不加租约时,客户端在两步之间崩溃,那笔预留就永久留在账上。
// TABLE_OF_CONTENTS
结论先行¶
任何"先扣一个估计值,等真实值到了再对账"的协议,都必须给那笔预留加租约。 不加租约时,客户端在两步之间崩溃,那笔预留就永久留在账上。
危险的地方不在于漏了一笔,而在于这个漏洞的形状:
- 租户的有效配额单调下降,一次崩溃扣掉一点,不会自己回来;
- 最终趋近于零,于是"什么都提交不了",但没有任何一条错误日志;
- 因为每一条单独的决策都是正确的——扣预留是对的,没收到提交所以不退款也是对的。
没有任何一步出错,系统却稳定地坏掉。 这类故障在事后复盘里极难定位, 因为所有的"检查某某步是不是错了"都会得到"没错"。
为什么¶
LLM 推理的准入是这个协议的典型场景,因为它同时满足两个条件:
- 成本的主要部分在决策时不可知。 prompt token 能数,输出 token 要等生成结束才知道。 网关必须在知道自己在决定什么之前就做出决定。
- 请求占用后端的时间以秒计。 于是客户端有充足的时间在预留和提交之间消失。
三阶段协议因此是被逼出来的,不是设计出来的:
reserve 为一个输出长度未知的请求占住预算
commit 按生成实际消耗的量对账,退还多占的部分
expire 回收那些主人再也没回来的预留
第三阶段不是"锦上添花的清理工作",它和前两阶段同等必要—— 它是唯一能把"崩溃"和"还在跑"区分开的机制。没有它,两者在账上长得一模一样。
两个容易做错的工程决定¶
对账要在一个原子脚本里做完。 把"补充额度 / 比较余量 / 扣减"拆成多次往返, 两个网关副本就会各自读到同一个剩余额度、各自放行,超发的倍数等于副本数。 Redis 的 Lua 脚本执行到结束才服务下一条命令,所以一次决策在分片上是串行的。
过期回收放在准入路径上扫,不要起后台任务。 每次调用扫一个有界的批次就够。 后台扫描器是另一个要部署、要监控、要选主的进程,而且它按自己的时钟还钱, 不是在有人需要的那一刻还钱。
怎么用¶
自查清单——任何"预扣 + later 对账"的地方都过一遍:
- 那笔预扣有没有过期时间? 没有就是上面那个故障。
- 过期回收由谁触发? 优先放在"有人需要额度"的那条路径上。
- 对账的三步(读余量 / 判断 / 扣减)是不是一个原子单元? 不是就会按副本数超发。
- 有没有一个指标能看出"被永久占住的预留"在涨? 这是唯一的早期信号—— 因为错误日志里什么都不会有。
- 把 4 的那个量和配额画在同一张图上。单调下降的曲线一眼就能看出来, 而分开看时它只是"用量变高了"。
更一般的形式:
当一个系统的每一步都正确、整体却单调退化时,去找那个"没有对称回收路径的分配动作"。
分配有、回收没有,中间又允许参与方消失,就一定是这个形状。
出处¶
~/Engineering/Rate-Limiter-Service(LLM Admission Gateway,Redis + Lua 三阶段协议,
公开仓库带可交互 demo 与 bench/ 下的实测产物)。
相关:最危险的工具故障是"静默少做"——技能在、后端不在、自审的失效模式不是"没发现",是"发现了没执行"。