System Design·2026年5月25日·10 分鐘閱讀

用訊息佇列解耦:我在金流與訂單系統的非同步設計

L
Louis Wu

後端工程師,用訊息佇列處理金流與訂單系統的非同步流程。

「一筆訂單成立後,要寄通知、更新庫存、產發票、加積分……」如果這些全部同步做完才回應使用者,那使用者要等很久,而且任何一步掛掉,整筆就失敗。訊息佇列就是用來解決這種問題的。這篇講我在金流與訂單系統裡,實際怎麼用訊息佇列做非同步解耦,以及它帶來的新問題。

為什麼要非同步

同步處理的問題是強耦合:主流程要等所有附帶的事都做完,而且綁死了所有下游的可用性。訊息佇列把這件事翻轉過來:主流程只要把「訂單成立了」這個事件丟進佇列就回應使用者,後續的通知、庫存、發票由各自的消費者非同步處理。

好處很實際:

  • 回應快:使用者不用等一堆附帶工作
  • 解耦:要新增一個「訂單成立後做的事」,只要多一個消費者去訂閱事件,完全不用動主流程
  • 削峰:尖峰時把瞬間湧入的工作攤平成佇列,消費者依自己的步調慢慢處理,下游不會被打爆
  • 容錯:某個消費者掛了,訊息還在佇列裡,等它復活再處理,不會整筆失敗

但非同步帶來新的難題

天下沒有白吃的午餐。非同步換來了上面的好處,但也帶來分散式系統的經典難題,這些你一定會遇到:

訊息會重複,消費者必須冪等 絕大多數佇列保證的是「至少送達一次」,也就是**同一則訊息可能被送到好幾次**。如果你的消費者不是冪等的,一則「扣款」訊息被重送,就會重複扣款。所以每個消費者都必須設計成「同一則訊息處理一次和處理十次,結果一樣」——通常靠訊息的唯一 id 去重。這點在金流場景是生死攸關的。

順序不保證 訊息的處理順序往往不保證。「付款成功」和「退款」的訊息,可能不照你期待的順序到。所以不能假設順序,要用狀態機判斷合法的狀態轉移(已退款的單收到「付款成功」要忽略)。

失敗的訊息怎麼辦 一則訊息一直處理失敗怎麼辦?不能讓它無限重試卡住整個佇列。常見做法是設定重試次數,超過就把它丟進「死信佇列」(dead letter queue)另外處理、告警,讓人來查,而不是默默吞掉或無限重試。

最終一致,不是即時一致 非同步意味著「主流程成功」和「附帶工作完成」之間有時間差。使用者下單成功了,但積分可能幾秒後才加上。系統設計和產品體驗都要能接受這種**最終一致**,不能假設一切都即時同步完成。

我的實務原則

  • 主流程只負責「把事件可靠地丟進佇列」,附帶工作交給消費者
  • 每個消費者都要冪等,用訊息 id 去重——這是不可妥協的
  • 不依賴訊息順序,用狀態機把關狀態轉移
  • 一定要有死信佇列與告警,別讓壞訊息無聲無息地消失
  • 設計上接受最終一致,並讓使用者體驗合理(例如顯示「處理中」)

小結

訊息佇列把同步的強耦合,換成非同步的解耦、削峰與容錯。但它不是免費的——它把你推進了分散式系統的世界,重複、亂序、失敗、最終一致這些問題全都要正面處理。

我的體會是:訊息佇列很強大,但用它之前要先想清楚「消費者冪等了嗎、順序假設拿掉了嗎、壞訊息有去處嗎」。把這幾件事做對,它就是讓系統可擴展又有韌性的關鍵基礎設施。

#訊息佇列#非同步#解耦#系統設計#高併發

相關文章