
为了压低长尾延迟,有些网关会在等待一段时间后再发一个备用请求。第二条请求有机会先回来,也有机会和第一条几乎同时完成。真正棘手的不是“谁先回包”,而是两边都回包以后,谁还能向用户写内容、谁负责结算,以及两个都失败时谁结束会话。
同一请求要有一个收口点
本文讨论首个成功结果获胜的本地状态机,不主张所有请求都双发。工具写入、支付等带副作用的操作,需要先解决幂等和授权;复制一次请求可能复制一次真实动作。Agent 护栏与审批文档把工具执行前的检查列为独立环节,不能因备用通道而跳过。
两个上游都成功时,只发布一个结果;一边失败时继续等另一边;两边都失败时明确结束。用户取消和截止时间也应进入同一个终态仲裁,不能另起一个“随便写异常”的旁路。

import java.util.concurrent.CompletableFuture;
public class Demo {
static class Race {
int remaining = 2;
final CompletableFuture<String> result = new CompletableFuture<>();
synchronized void complete(String value, Throwable error) {
if (remaining == 0) throw new IllegalStateException("extra callback");
remaining--;
if (result.isDone()) return;
if (error == null) result.complete(value);
else if (remaining == 0) {
result.completeExceptionally(new IllegalStateException("both failed"));
}
}
}
public static void main(String[] args) {
Race one = new Race();
one.complete("backup", null);
one.complete("late-primary", null);
if (!one.result.join().equals("backup")) throw new AssertionError();
Race two = new Race();
two.complete(null, new RuntimeException("first"));
if (two.result.isDone()) throw new AssertionError("failed too early");
two.complete("second", null);
if (!two.result.join().equals("second")) throw new AssertionError();
Race three = new Race();
three.complete(null, new RuntimeException());
three.complete(null, new RuntimeException());
if (!three.result.isCompletedExceptionally()) throw new AssertionError();
System.out.println("one winner; one failure tolerated; all failures terminate");
}
}
保存为 Demo.java,运行 javac --release 11 Demo.java 和 java Demo。这里的两次回调顺序是人为给定的,没有制造真实网络竞速。每个上游必须只回调一次;生产实现通常还要按 attemptId 去重,并把超时、取消和资源释放一起建模。
赢了不等于另外一边免费了
CompletableFuture 已完成,只能阻止本地重复发布。它不会自动取消输家的 HTTP 请求,更不会撤销对方已经生成的 Token。应记录主、备两个 attempt 的用量,分别调用传输层取消接口,并记录取消是否被确认;本地忽略迟到结果与上游停止工作是两件事。
对于流式响应,获胜点也需要提前定义。若先把主路首段文本发给用户,再选择备用的完整结果,就可能拼出两段互相冲突的答案。可以在首次对外提交前选择通道,提交后则使用明确的中断或续接协议。不要把字符串拼接包装成“无损恢复”。
双发是否划算,需要统计额外请求率、真正减少的尾延迟以及总费用。这里没有模型调用、线上延迟或省钱比例;只有一个可以离线检查的终态规则。
本地运行环境为 Java/javac 12.0.1,使用 --release 11 编译并执行了文中的检查;没有安装新依赖。该结果只覆盖示例列出的逻辑,不代表真实模型、浏览器或生产链路已经验证。
本文探讨为降低长尾延迟而采用的双发请求模式下,如何通过本地状态机实现结果终态仲裁:确保同一请求仅有一个收口点,处理赢/输请求的回调收敛、超时取消、幂等保障与流式响应一致性。强调输家请求不可忽略,需显式取消并记录用量,避免副作用重复执行。


被折叠的 条评论
为什么被折叠?



