🎯 什麼情境該想到我
當你「需要確保訊息一定會送達、即使發送方、接收方或訊息系統中途當機也不會遺失」的時候。
⚙️ 怎麼用(步驟 / 公式)
意圖:讓訊息系統把訊息持久化,保證即使發生故障,訊息仍會被投遞。
做法 / 結構要點:
- 訊息在傳遞過程中被寫入持久性儲存(磁碟),而非只留在記憶體。
- 系統重啟後可從儲存中恢復尚未投遞的訊息,繼續送達。
- 訊息在成功交給下一站前不會被移除,直到確認投遞完成。
★★ 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.ms | 120000(2 分鐘) | ★ 真正該調的是這個,不是 retries |
max.in.flight.requests.per.connection | 5 | ★ 開啟冪等時,≤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快得多。
🔗 相關工具
- 死信通道 Dead Letter Channel(搭配:即使持久保存仍投不出去時的收容處)
- 訊息通道 Message Channel(保證投遞是通道的一種傳遞品質設定)
- 訊息傳遞 Messaging(保證投遞是訊息傳遞可靠性的體現)
- 企業整合模式
- 冪等接收者 —— ★ 保證投遞的必然配套
- 死信通道 —— 重送到底仍失敗的去處
- 交易式用戶端 —— ack/交易邊界
- 持久訂閱者 —— 訂閱者離線期間也不丟
- 工具-Transactional-Outbox-Pattern —— ★ 解雙寫問題
- 工具-資料複製 —— 副本與 ISR 的原理
- 工具-預寫日誌與崩潰復原 —— 持久化為什麼可靠