// 請求頭或體攜帶
Idempotency-Key: biz_type:order:20240520:123456 // 業務唯一標識
// 服務端處理
public Result handle(Request req) {
String key = req.getHeader("Idempotency-Key");
if (redis.setIfAbsent(key, "PROCESSING", 30, TimeUnit.MINUTES)) {
try {
doBusiness(req);
redis.set(key, "SUCCESS");
return success();
} catch (Exception e) {
redis.delete(key); // 失敗釋放鎖,允許重試
throw e;
}
}
String status = redis.get(key);
if ("SUCCESS".equals(status)) return success("已處理");
return conflict("正在處理中"); // PROCESSING 狀態
}
enum Status { INIT, SENDING, SUCCESS, FAILED, COMPENSATING }
@Table
class HttpTask {
String idempotencyKey;
String payloadJson;
Status status;
int retryCount;
LocalDateTime nextRetryTime;
// ...
}
// 定時掃描 INIT/FAILED/COMPENSATING 且 nextRetryTime <= now 的任務發送
// 發送前更新 status=SENDING
// 成功更新 SUCCESS,失敗 retryCount++、計算下次退避時間
// 超過最大重試進入 COMPENSATING(人工/異步補償)
// 重試間隔 = min(base * 2^retry + random(0, jitter), maxInterval)
int base = 1000; // 1s
int maxInterval = 60000; // 1min
int maxRetries = 5;
long nextInterval = Math.min(base * (1L << retryCount) + random.nextInt(500), maxInterval);
// 使用 Resilience4j / Hystrix / Sentinel
CircuitBreaker cb = CircuitBreaker.ofDefaults("http-client");
Supplier decorated = CircuitBreaker.decorateSupplier(cb, () -> httpClient.post(url, body));
Try.ofSupplier(decorated)
.retry(3, Schedule.of(1000, 2.0)) // 重試 3 次,指數退避
.onFailure(e -> log.error("熔斷/重試耗盡", e))
.get();
| 風險 | 對策 |
|---|---|
| 網絡抖動導致超時 | 設置合理連接/讀取超時(如 3s/10s),啟用重試 |
| 對端處理超時但實際成功 | 幂等鍵去重,發送端狀態機最終一致性 |
| 對端異常拋錯 | 區分可重試錯誤(5xx, timeout)與不可重試(4xx, 業務拒絕) |
| 大量積壓導致發送端 OOM | 任務持久化到 DB/Redis,消費者拉取處理,背壓控制 |
| 數據不一致無法自動修復 | 定期對賬任務(T+1 全量比對),差異進入人工補償池 |
HTTP 直連穩定性 = **幂等鍵去重** + **發送端狀態機持久化** + **指數退避重試** + **熔斷防雪崩** + **定期對賬補償**。不引入 MQ 也能構建高可靠同步鏈路,關鍵在於**將「不可靠的網絡」轉化為「可靠的狀態流轉」**。