Back to Blogs
rabbitmq
RabbitMQ 中 Message Persistence and Durability
如何透過 Durable Queue、Persistent Message 與 Publisher Confirms 三者搭配,確保訊息在 Broker 重啟後不遺失
Aaron
Message 在 RabbitMQ 有哪些情況會丟失呢?
- producer 根本沒有正確的把 Message 丟到 Exchange
- 當 Queue 重啟消失後, 裡面的 Message 自然會丟失
- 當 Queue durable 設定
true, 重啟後 Queue 還在, 但是 Message 預設只寫到記憶體, 所以重啟後 Queue 中的 Message 丟失 - 如果 fsync 寫入磁碟之前 crush, 那麼等於 message 丟失
- Consumer 有沒有正常接收到呢? 如果 RabbitMQ 以為 Message 正常接收到丟失了
- Consumer nack/reject 該 Message, 但是沒有 DLX / requeue 該 Message 就丟失了
How to Solve ?
Producer 端
要如何確保 Message 有正確被 RabbitMQ 接收到並且成功寫入磁碟呢? 這有兩個問題:
- Exchange 有沒有 Binding 到任何 Queue ? 因為儲存訊息是在 Queue 不是 Exchange
- Message 到 Queue 有沒有正確寫入磁碟呢?
await channel.publish(EXCHANGE, "order.created.vip.asia", buffer, {
persistent: true, // 設定 Message 進到 Queue 後要寫入磁碟
mandatory: true, // 要求「至少一個 queue 收到,否則退回」
})
channel.on("return", (msg) => {
// 當 mandatory:true, 然後沒有 Queue binding 則會退 Message 回 Producer, 需要 listen basic.return 去處理
})
// ack: 代表 代表 broker 已**完整處理**。對 persistent message 來說 ack 代表訊息已寫入磁碟(fsync 完成),queue durable 的話 broker 重啟也不丟
// nack: 代表 broker 收到但無法處理——通常是 broker 內部問題(磁碟寫入失敗、資源耗盡、異常狀態), 這時候就會 raise error
await chan.waitForConfirms()
Queue 端
- 如何確保 Queue 重啟後會存在呢?
// 設定 Queue durable 為 true
await channel.assertQueue(QUEUE, { durable: true });
Consumer 端
- 如何確保 Consumer 有正確拿到 Message ?
- 如果 Consumer nack 該如何處理?
// 設定 Queue DLX 確保有處理 requeue false 的 message
await channel.assertQueue(QUEUE, { durable: true, deadLetterExchange: 'deadLetterExchange' });
const { consumerTag } = await channel.consume(QUEUE, async (msg: ConsumeMessage | null) => {
try {
process(msg)
channel.ack(msg);
} catch (err) {
// 需不需要 requeue 重試一遍
// 不需要則進入 DLX 去處理
channel.nack(msg, false, !msg.fields.redelivered);
}
},
// 關掉 autoAck, 當 Consumer ack 後才會把 Queue 中的 Message 移除掉
{ noAck: false }
);
總結
Durability 是靠整條鏈的設定來保證
- Producer → broker:Publisher Confirms + mandatory flag
- Broker 寫入磁碟:durable queue + persistent message
- 主 queue → DLX:沒有 publisher confirm,只能靠正確配置 + 測試 + 監控
- Broker → consumer:manual ack