🎯 什麼情境該想到我

當你「需要確保訊息一定會送達、即使發送方、接收方或訊息系統中途當機也不會遺失」的時候。

⚙️ 怎麼用(步驟 / 公式)

意圖:讓訊息系統把訊息持久化,保證即使發生故障,訊息仍會被投遞。

做法 / 結構要點:

  • 訊息在傳遞過程中被寫入持久性儲存(磁碟),而非只留在記憶體。
  • 系統重啟後可從儲存中恢復尚未投遞的訊息,繼續送達。
  • 訊息在成功交給下一站前不會被移除,直到確認投遞完成。

★★ Kafka 現在預設就是「持久化 + 生產端冪等」——舊建議已過期

查證 Kafka 4.0 官方 producer configs(2026-09-25):

設定★ 現在的預設值意思
acks★ all等所有 in-sync replica 確認。這是最強保證
enable.idempotence★ true生產端重試不會產生重複(broker 用 producer id + sequence 去重)
retries★ 2147483647實際由 delivery.timeout.ms 控制
delivery.timeout.ms120000(2 分鐘)★ 真正該調的是這個,不是 retries
max.in.flight.requests.per.connection5★ 開啟冪等時,≤5 仍保證順序

★★ 「記得把 acks 設成 all」這條經典建議已經過期——它現在是預設值。
而且啟用冪等要求 acks=all(官方文件明說:conflicting configuration 會出錯)。
★ 舊文章/舊教材裡的 acks=1 + retries=3 組合,今天反而是降級設定。

★ 但生產端冪等只解決「producer 重試造成的重複」,不解決:

  • 應用程式重啟後重發同一則(producer id 變了)→ 需要 冪等接收者。
  • 你的處理有外部副作用 → 「恰好一次」不存在。

★ 完整的「不丟訊息」要三段都守住

① 生產端 → broker
   ★ Kafka:acks=all(預設)+ 等 Send 的回應再回覆呼叫端
   ⚠️ 非同步 Send 不檢查 callback = 靜默丟失
   ★ RabbitMQ:publisher confirms(必須開,預設不開)

② broker 內部
   ★ Kafka:replication.factor ≥ 3 + min.insync.replicas = 2
     → ⚠️ 只設 acks=all 但 min.insync.replicas=1 → ★ 保證形同虛設
   ★ RabbitMQ:quorum queue(Raft 複製)+ durable queue + persistent message
     → ⚠️ 三者缺一都會丟

③ ★★ 應用程式狀態 → broker(最容易漏的一段)
   「寫資料庫成功、發訊息失敗」→ 資料與訊息不一致
   → ★ 這一段不是 broker 能解的,要用 [[工具-Transactional-Outbox-Pattern]]

★★ 最大的盲點:第 ③ 段(雙寫問題)

// ❌ 雙寫:這兩件事不可能同時原子
tx.Save(order)          // ★ 成功
producer.Send(event)    // ★ 失敗 → 訂單存在但下游永遠不知道
// 反過來也一樣糟:發了訊息但交易 rollback → 下游收到不存在的訂單
 
// ✅ Outbox:同一個交易寫業務資料 + 待發訊息
tx.Save(order)
tx.Save(outboxRow{Topic: "orders.order.placed", Payload: body})
tx.Commit()             // ★ 兩者原子
// 另一個 relay 程序:讀 outbox → 發送 → 才標記已發送
// ★★ 順序必須是「先發送、後標記」 —— 反了就會丟訊息

→ 完整做法見 工具-Transactional-Outbox-Pattern。

★ 代價:保證投遞不是免費的

代價說明
★ 延遲acks=all 要等所有 ISR;p99 明顯高於 acks=1
★ 吞吐每則訊息都要 fsync/複製
★ 儲存持久化 + 副本 = 3 倍磁碟
★★ 重複保證「不丟」的手段就是「重送」⇒ 必然會重複 ⇒ 消費端必須冪等

★★ 這是最重要的一句:保證投遞的代價就是重複。
想「不丟又不重」= 恰好一次 = 在有外部副作用時不存在。
→ 接受至少一次,把 冪等接收者 做好。 這是唯一務實的路線。

★ 什麼時候刻意不要保證投遞

  • ★ 遙測/指標/點擊流——丟 0.01% 完全無所謂,而 acks=0 快得多。
  • ★ 可以從來源重算的資料——重放比保證便宜。
  • 時效性強過完整性:即時報價丟一筆比延遲一秒好。

🧪 我實際套用的紀錄

  • (待填)

⚠️ 注意 / 什麼時候不適用

  • 代價是效能:寫磁碟比純記憶體慢,吞吐量下降、延遲上升。

  • 若訊息可容忍偶爾遺失、或講求極高吞吐/低延遲,未必需要開啟保證投遞。

  • ★★ 「記得設 acks=all」已經是預設值(Kafka 4.0,2026-09-25 查證)。enable.idempotence 預設也是 true。舊教材的 acks=1+retries=3 今天是降級設定。

  • ★★ acks=all 配 min.insync.replicas=1 等於沒有保證。兩個要一起設。

  • ★ RabbitMQ 的 publisher confirms 預設不開;durable queue + persistent message + quorum queue 三者缺一都會丟。

  • ★ 非同步 Send 不檢查 callback/error 就是靜默丟失。

  • ★★ 最容易漏的是「應用程式狀態 → broker」這一段(雙寫問題),broker 解不了,要用 Outbox。

  • ★★ Outbox relay 必須「先發送、後標記」。 反了就會丟訊息。

  • ★★ 保證投遞的代價就是重複。 想不丟又不重(恰好一次)在有外部副作用時不存在。

  • 調 retries 沒有意義,該調的是 delivery.timeout.ms。

  • 遙測類資料刻意不要保證投遞,acks=0 快得多。

🔗 相關工具