不盲目跟風:訊息代理選型實戰指南

作者 | Nehme Bilal
譯者 | 馬可薇
策劃 | Tina
訊息系統在現代分散式架構中扮演關鍵角色:建立可靠通訊管道、支撐非同步處理流程,以及實現元件間松耦合。此外,訊息系統也可提升應用的可用性,應對流量峰值。目前的訊息代理有基於流處理的也有佇列型別的服務,這些各有其優缺點。
根據作者和多個工程團隊的合作經驗,訊息代理的選型往往缺乏系統的方法論支援。這方面的決策往往受技術潮流的風向、個人喜好或特定技術的便利性驅動,而不是基於應用場景的客觀需求。但科學的選型應該是將代理的技術特性與業務需求相匹配,這也正是本文的關注重點。
我們將深入剖析兩大主流方案:Apache Kafka(流式架構)與亞馬遜 SQS(佇列架構),這二者也正是 EarnIn 主要採用的兩大訊息代理。透過探討它們的技術特性與常見訊息模式的適配度,本文將為讀者構建一個決策的參考框架;在掌握了這一分析正規化後,開發者將能更好地評估其他訊息場景和代理方案,最終能選定與業務需求契合度最高的技術方案。
訊息代理
本章節將聚焦行業主流訊息代理方案,橫向對比其核心特性,在理解了不同訊息代理間差異後,我們才能判斷哪些代理技術更加適配現代應用中的典型訊息模式。需要說明的是,本文中不涉及各類代理技術的深度原理解析,不熟悉相關技術的讀者朋友可以參考官方文件以獲取更詳細的資訊。
亞馬遜 SQS(簡單佇列服務)
亞馬遜 SQS 作為一款全託管訊息佇列服務,有效地簡化分散式系統中解耦元件間的通訊流程,該服務在保障訊息可靠傳遞的同時,將基礎設施管理、彈性擴充套件及錯誤處理等複雜邏輯完全封裝。其核心特性如下:
訊息生命週期管理機制:SQS 支援單條訊息或最多十條的小批次訊息管理。每條訊息可根據業務需求執行接收、處理、刪除或延遲操作。一般來說,應用接收訊息,完成處理後會將其從佇列中刪除,這一機制確保了訊息處理的可靠性。
盡力有序性保障: 標準佇列雖然是按傳送順序投遞訊息,但卻無法保證訊息嚴格的順序性,尤其在重試或並行消費場景下。這種設計可以在非嚴格順序需求場景下允許更高吞吐量,但若業務需求嚴格的順序保障,可選擇 FIFO SQS(先進先出佇列)從而實現特定順序的訊息處理(詳見 更多 有關 FIFO 佇列解析)。
內建死信佇列(DLQ)支援:SQS 原生整合死信佇列功能,可自動隔離無法處理的異常訊息,避免其對正常訊息流造成阻塞。
讀寫吞吐能力: 該服務提供近乎無限的讀寫吞吐量,特別適合需要高效處理海量訊息的高併發場景,確保系統在流量激增時仍保持穩定。
消費者自動擴縮:SQS 支援基於佇列深度的自動擴縮資源計算(如 AWS Lambda、EC2 或 ECS 服務),具體可參見 官方文件。消費者可根據佇列訊息數量動態擴縮容:流量高峰時自動擴容,負載降低時自動縮容。這種全自動的彈效能力讓系統能夠自主應對各類無規律的流量波動而無需人工干預。
釋出 – 訂閱(pub-sub)支援: 雖然 SQS 本身採用點對點通訊模型(每條訊息僅被單一消費者處理),不支援“釋出 – 訂閱”模式,但透過與亞馬遜 Simple Notification Service(SNS)整合就可實現釋出 – 訂閱架構:訊息釋出至 SNS 主題後,可同時分發至多個訂閱該主題的 SQS 佇列,使不同消費者能獨立處理同一訊息,從而基於 AWS 服務棧構建完整的釋出 – 訂閱系統。
亞馬遜 FIFO SQS
FIFO SQS 在標準 SQS 基礎上進行了功能上的增強,透過“訊息組”的邏輯分割槽機制確保了訊息嚴格有序的處理。該服務特別適用於需要保持事件順序的情況,例如使用者個人通知流、金融交易處理等對訊息順序有嚴格要求的場景。
訊息組分割槽的邏輯隔離機制:FIFO SQS 為每條訊息設定了 MessageGroupId 標識,並透過該標識在佇列內建立邏輯分割槽。相同 MessageGroupId 的訊息組將嚴格按序處理,而不同訊息組之間則可並行消費。以使用者行為處理場景為例(如使用者觸發的一系列通知或動作),為每個使用者分配獨立 MessageGroupId 後,SQS 能保證該使用者的所有關聯訊息嚴格按照入隊順序處理,同時其他使用者的訊息(不同 MessageGroupId)仍可並行處理;在保障了系統高吞吐量的同時又可確保使用者相關訊息的順序性。相較於標準 SQS 或 Apache Kafka、Amazon Kinesis 等流式訊息代理,FIFO SQS 在這方面體現出了其獨特的價值。
死信佇列(DLQ):FIFO SQS 雖然內建了死信佇列支援,但使用過程卻還是需要極其小心,因其可能會破壞訊息組的嚴格順序性。比如說同屬一個 MessageGroupId 的兩條訊息message1 和 message2 先後入隊時,如果 message1 處理失敗轉入 DLQ,message2 仍可被成功處理。這種情況將破壞訊息組內的順序性保障,違背 FIFO 佇列的核心設計目標。
毒丸訊息隔離: 在未啟用 DLQ 時,FIFO SQS 會無限重試失敗的訊息。雖然這也是維持了嚴格的順序性,但卻會帶來連鎖的阻塞反應:同一訊息組內的所有後續訊息的處理都將無法進行,直到該失敗訊息最終成功處理或被手動刪除。反覆處理失敗的訊息被稱作是“毒丸訊息”,在部分訊息系統中,毒丸訊息可阻塞整個佇列或分割槽,導致後續所有訊息都無法被處理。但是在 FIFO SQS 中,毒丸的影響範圍被限制在單一的訊息組(邏輯分割槽)中,只要訊息組的設計合理,這種隔離機制就能顯著降低故障的影響範圍。
若要最大限度地減少影響,關鍵在於 MessageGroupId 的合理選擇:既要保證邏輯分割槽足夠小,還要確保需求有序處理的訊息處於同一分割槽中。舉例來說,多使用者應用中使用 user ID 作為 MessageGroupId 可確保這類故障只會影響當前使用者相關的訊息;在電商應用中,使用訂單 ID 作為 MessageGroupId 可以確保失敗訂單不會影響其他客戶的訂單。
透過以下場景我們可以直觀地看出毒丸隔離的效果:
  • 無隔離或僅分割槽級隔離時,一個毒丸訊息可能阻塞區域內的所有訂單(比如某國的全部亞馬遜訂單)。
  • 採用 FIFO SQS 隔離時,僅單個使用者的訂單會受影響,其他訂單仍可正常處理。
因此,毒丸訊息隔離是 FIFO SQS 的一重要特性,顯著提升了分散式訊息系統的容錯能力。
吞吐量:FIFO SQS 的預設吞吐量限制為每秒 300 條訊息,啟用高吞吐量模式則能將這一限制提高到每秒九千條訊息。要想能達到如此之高的吞吐量,就需要仔細設計訊息組從而實現足夠的並行處理能力。
消費者自動擴縮: 與標準 SQS 相類似,FIFO SQS 也支援根據佇列內訊息數量自動擴縮計算資源。雖然 FIFO SQS 的擴充套件性和訊息組(邏輯分割槽)數量掛鉤,並非是真正的無限制,但也可以透過設計非常多的訊息組來提升其能力(比如為每個使用者都分配一個訊息組)。
釋出 – 訂閱支援: 與標準 SQS 類似,FIFO SQS 也可透過搭配支援 FIFO topic 的 SNS 來實現釋出 – 訂閱功能。
Apache Kafka
Apache Kafka 是一款開源的分散式流處理平臺,專為即時事件流處理和高吞吐應用而設計。與 SQS 等傳統訊息佇列不同,Kafka 是採用基於流的處理模式,消費者透過 offset(偏移量)來追蹤消費的進度。在 Kafka 中,消費者可以向前移動 offset(或向後移動以實現訊息重放),也可批次提交多條訊息。這種基於 offset 的機制是 Kafka 和傳統訊息佇列的關鍵區別,後者往往需要獨立處理和確認每一條訊息,以下是 Kafka 的核心特性:
物理分割槽機制:Kafka 的主題(topic)在建立時就被劃分為多個物理分割槽(也稱“分片”),每個分割槽維護獨立的 offset 且自主管理訊息順序。雖然 Kafka 支援動態增加分割槽,但這種操作可能會破壞訊息的順序性,需謹慎處理。減少分割槽則更為複雜且通常來說應當避免此操作,因為這會影響資料的分佈和消費者負載均衡。考慮到分割槽策略可直接影響系統的擴充套件性和效能,這點應當在設計初期就進行仔細歸規劃。
原生髮布 – 訂閱支援:Kafka 原生支援釋出 – 訂閱模型,允許多個消費者組獨立消費同一主題,讓不同應用或服務能互不干擾地處理相同資料。每個消費組都能有獨立的主題檢視,這種設計為訊息生產者和消費者提供了靈活的擴充套件能力。
高吞吐量和批次處理:Kafka 針對高吞吐量場景進行了專門的最佳化,使其能夠高效地處理海量資料。透過合併資料庫寫入和 offset 提交操作,消費者可以批次處理訊息從而顯著降低系統開銷(如單次處理上萬條訊息)。這種批次處理機制是流式系統與佇列系統的本質區別,後者往往需要逐條或小批次處理訊息。
訊息回放能力:Kafka 會按配置保留訊息(預設保留七天),允許消費者透過回退 offset 實現訊息回放。這一特性在除錯、歷史資料重新處理或應用故障恢復等場景尤為實用;消費者可以自主控制處理進度,必要時也能進行訊息重試,這讓 Kafka 極其適合需要資料具備持久化和容錯能力的場景。
毒丸訊息處理:在 Kafka 中,毒丸訊息會阻塞其所在的全部物理分割槽,導致該分割槽內的所有訊息處理延遲。以電商系統為例,若是按照地區劃分 Kafka 分片,那麼單條毒丸訊息就可能會導致整個地區的訂單處理停滯,從而造成重大業務影響。這種設計缺陷凸顯出了嚴格物理分割槽和佇列系統(如 FIFO SQS)邏輯分割槽的關鍵差異:後者能將故障隔離在更小的訊息組範圍內。如果對嚴格的訊息順序沒有要求,則可以透過死信佇列隔離毒丸訊息,避免其阻塞分割槽內所有的訊息處理。
自動擴縮限制:Kafka 的擴充套件性受其分割槽模型的限制,每個分割槽(分片)需保持嚴格的順序性,且同一時間只能由一個計算節點處理。也就是說,增加的計算節點若是超過了分割槽數量,那麼額外的節點將處於閒置狀態,吞吐量無法得到提升。因此,Kafka 與自動擴縮消費者模式的適配性較差,活躍的消費者數量實際受限於分割槽數量。相較於 FIFO SQS 等採用邏輯分割槽的訊息系統,Kafka 無法透過細粒度分割槽實現更為靈活的消費者擴充套件,在動態擴縮場景中顯得較為侷限。
訊息代理對比
訊息模式與其對代理選型的影響
在分散式系統中,訊息模式決定了服務之間的通訊和資訊處理方式,不同模式對訊息的順序性、可擴充套件性、錯誤處理或並行性等都有不同的要求,這些要求直接影響了訊息代理的選擇。本文中將重點分析三種典型訊息模式:命令模式(Command Pattern)、事件攜帶狀態轉移(ECST)和事件通知模式(Event Notification Pattern),並探討這些模式與亞馬遜 SQS、Apache Kafka 等主流訊息代理的適配性,這型別的分析框架同樣適用於其他訊息模式的選型分析。
命令模式
命令模式是一種將請求或操作封裝為獨立物件的設計正規化,這些命令物件透過訊息代理進行非同步處理,允許傳送方無需等待相應便可繼續執行操作。
透過命令的持久化和自動重試機制,這種設計模式確保了系統的可靠性。即使消費者暫時不可用,生產者也能持續執行。此外,消費者還可以根據自身能力調節消費速率,從容應對流量峰值。
需要注意的是,命令處理通常包含了複雜的業務邏輯,其中可能設計資料庫事物、外部 API 呼叫等操作,因此其實現需要有可靠的並行處理、自動擴縮,以及完善的毒丸訊息處理能力。
核心特性
多來源、單一目的地:命令可由一個或多個服務產生,但通常僅由單個服務消費。每條命令通常只需處理一次,多個消費者節點透過競爭機制獲取命令。因此,命令模式通常不需要釋出 – 訂閱功能支援。
高吞吐量:多個生產者可以高速率生成命令,因此所選的訊息代理必須能支援高吞吐量和低延遲,才能確保命令的生成不會成為上游服務的效能瓶頸。
消費者自動擴縮:命令處理在消費端往往涉及耗時操作(如資料庫寫入和外部 API 呼叫),為避免資源爭用,命令的並行處理至關重要。訊息代理選擇方面需能支援消費者並行獲取命令並獨立處理,不受少量並行工作流(如物理分割槽)的限制。透過這種水平擴充套件能力,系統可根據命令吞吐量的波動進行動態調整,在高峰期間增加消費者例項以應對需求,在低活躍期間縮減規模以最佳化資源利用率。
毒丸訊息風險:命令的處理通常涉及複雜的工作流程和網路呼叫,這會增加處理失敗的可能性,進而增加毒丸訊息的可能性。為緩解此風險,訊息代理必須要能支援高基數的毒丸隔離機制,確保失敗的訊息僅會影響一部分命令,而不會破壞整體系統的執行。透過將毒丸訊息隔離到獨立的訊息組或分割槽中,系統可以在保障可靠性的同時,繼續高效處理其他未受影響的命令。
訊息代理適配性
考慮到並行消費、自動擴縮和毒丸隔離等需求,Kafka 並不適合處理命令:正如前文所述,Kafka 的物理分割槽數量固定且無法動態擴充套件。此外,一條毒丸訊息就有可能阻塞整個物理分割槽,從而影響大量使用者。
如果對順序沒有要求,標準 SQS 是消費和處理命令的絕佳選擇:支援無限吞吐量的並行消費、動態擴縮,還能透過死信佇列(DLQ)隔離毒丸訊息。
對於需保證順序和劃分到多個邏輯分割槽的情況,FIFO SQS 是不錯的方案。透過策略性地選擇訊息組 ID 來建立大量小型邏輯分割槽,系統可以實現近乎無限的並行性和吞吐量。此外,任何毒丸訊息也只會影響單個邏輯分割槽(例如應用內的一個使用者),確保其影響被隔離到最小。
事件攜帶狀態轉移(ECST)
事件攜帶狀態轉移 模式是分散式系統中用於實現資料複製和去中心化處理的設計方法。該模式以事件作為服務或系統間狀態變更傳遞的主要機制,每個事件中都包含其他元件更新其本地狀態所需的全部資訊(狀態),使其無需依賴對源服務的同步呼叫。
透過服務間解耦並減少即時通訊的需求,ECST 模式增強了系統彈性,使得各元件在系統部分服務暫不可用時仍能獨立運作。此外,ECST 還透過將資料複製到需要的地方,讓服務可以依賴本地狀態副本而無需反覆呼叫源系統 API,有效地減輕了源系統負載。這種模式特別適用於事件驅動框架和接受最終一致性的場景。
核心特性
單一來源,多目的地:在 ECST 模式中,事件由狀態所有者釋出,由多個需要複製該狀態的領域或服務消費;這要求訊息代理必須支援釋出 – 訂閱模式。
低毒丸訊息風險:毒丸訊息風險低:由於 ECST 模式很少涉及業務邏輯,通常情況下會避免呼叫其他服務的 API,毒丸訊息的風險可以忽略不計。因此,在該模式中通常不需要使用死信佇列(DLQ)。
批次處理:作為一款資料複製模式,ECST 極大受益於批次處理:大批次複製資料能顯著提升效能並降低成本,尤其是在目標資料庫支援單次操作批次插入的情況下。將高效大批次提交的訊息代理與針對批次操作最佳化的資料庫相結合,可以大幅提升應用效能。
嚴格順序性:ECST 通常嚴格要求訊息的順序,以確保領域內的實體狀態是按照正確的順序被複制;這樣能防止舊版本的實體覆蓋新版本。當事件攜帶增量變更(例如“設定某屬性”)時,順序性尤為關鍵,因為亂序事件不能簡單地被丟棄。支援嚴格順序的訊息代理可以極大地簡化事件消費過程並確保資料完整性。
訊息代理適配性
考慮到要支援釋出 – 訂閱、嚴格順序性和批次處理等需求,再加上低毒丸訊息風險的特點,Apache Kafka 成為了 ECST 模式的理想選擇。Kafka 允許消費者單次操作大批次訊息並提交 offset,比如單次處理 10,000 條事件、批次寫入資料庫(需資料庫支援批次寫入)、透過單次網路呼叫完成提交;這種機制使得 Kafka 在此類場景下的效率顯著優於亞馬遜 SQS。此外,低毒丸訊息風險也消除了其對死信佇列(DLQ)的依賴,大幅簡化了錯誤處理流程。除批次處理的優勢外,Kafka 的分割槽機制還能透過事件分片提高整體吞吐量。
不過,如果目標資料庫不支援批次操作,資料庫寫入可能會成為效能瓶頸,這種情況下 Kafka 的批次提交優勢難以得到體現。針對這種場景,可以將 Kafka 訊息匯入 FIFO SQS 佇列,或直接使用 FIFO SNS 和 SQS 的組合(不使用 Kafka)。如前文所述,FIFO SQS 透過細粒度的邏輯分割槽實現了並行處理與順序保障的雙重目標,這種設計可以透過增加消費者節點實現動態擴充套件,確保系統在高負載下仍能維持高效處理能力。
事件通知模式
事件通知模式允許服務將系統中發生的重大事件通知到其他服務,一般來說通知都是簡短且只包含事件必要資訊的(如一個識別符號)。為了處理通知,消費者往往需要呼叫 API 從源服務或其他服務中獲取更多詳細資訊。此外,消費者可能也需要進行資料庫更新、建立命令或釋出其他系統中可消費的通知。這種模式在分散式架構中提供了松耦合和即時相應的能力,但由於通知處理的潛在複雜性(如 API 呼叫、資料庫更新和事件釋出),其可擴充套件性和錯誤處理能力都是必須要考慮的關鍵因素。
核心特性
事件通知模式的核心特性與命令模式的有明顯重疊部分,主要體現在處理複雜且耗時任務的通知時。在這些情況中,事件通知模式的實現需要支援並行消費、自動擴縮消費者和毒丸隔離才能實現可靠和高效的處理。此外,該模式還需支援 PubSub,以實現事件的一對多分佈。
在處理通知時,某些情況下工作流會較為簡單,如更新資料庫或向下遊系統釋出事件。這類情況下,事件通知模式的特性更接近於 ECST 模式。
需要注意的是,同一通知的不同消費者可能會有不同的處理方式,可能會出現消費者 A 需要執行復雜的處理,消費者 B 則在執行簡單到幾乎不可能失敗的任務。
訊息代理適配性
在通知的消費者和消費命令的特性相符時,SQS(或 FIFO SQS)自然是個好選擇。但如果消費者只需要處理簡單的資料庫更新時,Kafka 的消費通知可能更高效,因為 Kafka 是能夠批次處理通知並進行大規模批處理提交的。
通知處理方面的挑戰之一是往往無法提前預測消費者的消費模式,導致在生產通知時究竟是選擇 SNS 還是 Kafka 變得很困難。
在靈活性方面,我們在 EarnIn 決定使用 Kafka 作為唯一的訊息代理來發布通知。如果消費者需要藉助 SQS 的特性進行消費,那就可以透過 AWS EventBridge 將資訊從 Kafka 路由到 SQS。如果消費者不需要 SQS 的特性,就能直接從 Kafka 消費,並享受到其高效批處理的能力。此外,使用 Kafka 而非 SNS 來發布通知,就算最後還會將訊息路由到 SQS 進行消費,消費者也可以享受到 Kafka 的重放能力。
最後,由於 Kafka 也適合於 ECST 模式,而命令模式不需要支援 PubSub,這讓我們沒理由繼續使用 SNS。因此,我們將 Kafka 作為唯一的 PubSub 代理,極大簡化了我們的工作流:在所有事件都經流 Kafka 後,我們就可以構建工具,將 Kafka 事件複製到 DataLake 中進行除錯、資料分析、重放或回填等用途。
結   論
要想選擇適合的應用程式的訊息代理,就需要了解可用選項的特點以及要使用的訊息模式,要考慮的關鍵因素包括:流量模式、自動擴充套件能力、對毒丸訊息的容忍度、批次處理需求以及順序要求。
本文雖以 Amazon SQS 和 Apache Kafka 為例,但更宏觀的選型本質是在佇列與流式架構間做出抉擇。不過,透過組合使用二者優勢也是可行方案。
採用單一代理作為事件生產的標準,可以讓企業集中資源在一套系統上構建工具鏈、複製機制和監控體系,降低維護標準。消費者端則可以透過 EventBridge 等服務將訊息路由至最合適的代理進行處理,從而在褒詞運維效率的同時獲得架構靈活性。
今日好文推薦

相關文章