實時交易系統,平時 QPS 1000,雙十一峰值數十萬。採用 Kafka + Storm 架構:交易數據寫入 Kafka → Topology A 消費處理規則匹配 → 推送不同通知主題 → Topology B 消費推送至客戶端(微信/支付寶)。

topology.message.timeout.secs = 180 (3分鐘)spout.pending.max = 2000public void execute(Tuple tuple) {
String transactionId = tuple.getStringByField("transactionId");
// Redis SETNX 原子去重
if (redis.setnx("tx:processed:" + transactionId, "1")) {
redis.expire("tx:processed:" + transactionId, 86400); // 24h 過期
collector.emit(tuple); // 轉發給 CalculateBolt
} else {
// 已處理,直接 ack 不再下發
collector.ack(tuple);
}
}
CalculateBolt 匹配規則 → 推送通知 Kafka 主題。
運行半年後,用戶收到重複通知(2-3 條相同內容)。
Storm ACK 機制只能保證 至少一次 語義。超時重發 + 原請求未真正失敗 = 重複處理。業務層冪等性/去重是唯一根治方案。
在 Topology B 入口 Bolt 添加相同 Redis 去重邏輯,Key 設計:notify:processed:{notificationId}。
通知消息攜帶 bizId(如訂單號+通知類型),下游推送接口基於 bizId 冪等處理(數據庫唯一索引/Redis SETNX)。
public class SmartAckBolt extends BaseRichBolt {
@Override
public void execute(Tuple tuple) {
String txId = tuple.getStringByField("transactionId");
try {
// 業務處理
processBusiness(tuple);
// 成功:標記為正常完成,防止超時重發時再次處理
redis.setnx("tx:success:" + txId, "1");
redis.expire("tx:success:" + txId, 86400);
collector.ack(tuple);
} catch (BusinessException e) {
// 業務異常:標記為異常,允許重試
redis.setnx("tx:failed:" + txId, "1");
collector.ack(tuple); // 不重試業務邏輯錯誤
} catch (Exception e) {
// 系統異常:不 ack,讓 Storm 重試
collector.fail(tuple);
}
}
}
// OnceBolt 升級版:三態判斷
public void execute(Tuple tuple) {
String txId = tuple.getStringByField("transactionId");
if (redis.exists("tx:success:" + txId)) {
// 正常完成態:超時重發導致的重複,直接丟棄
collector.ack(tuple);
return;
}
if (redis.exists("tx:failed:" + txId)) {
// 異常失敗態:允許重試處理
collector.emit(tuple);
return;
}
// 首次處理態:執行去重鎖
if (redis.setnx("tx:processing:" + txId, "1")) {
redis.expire("tx:processing:" + txId, 300); // 5分鐘處理窗口
collector.emit(tuple);
} else {
collector.ack(tuple); // 並發處理中,丟棄重複
}
}
| 方案 | 重複來源覆蓋 | 實現複雜度 | 業務影響 |
|---|---|---|---|
| 僅 Topology A 去重 | Kafka 重複生產、Spout 重發 | 低 | 無法防禦下游超時重複 |
| 雙層去重 (A+B) | + Topology B 超時重發 | 中 | 完全解決,但 Redis 連接數翻倍 |
| 三態智能去重 | 全覆蓋,區分異常/超時 | 高 | 最優:異常可重試,超時不重複,資源最省 |
| 下游冪等接口 | 終極兜底 | 需下游配合 | 架構最乾淨,推薦作為最終防線 |
topology.message.timeout.secs,預留 2-3 倍餘量bizType:bizId 確保全局唯一,避免不同業務衝突Storm 高可靠 ≠ 無重複。ACK 機制解決「不丟失」,冪等去重解決「不重複」。交易系統通過 Redis 多態去重 + 下游冪等接口 雙重保障,實現「消息必達、業務不重」的強一致性體驗。架構設計時必須考慮 超時重發導致的雙重執行 這一分布式系統固有特性,而非僅關注異常重試。