在 Spanner 等分散式雲端資料庫中建構可靠的訊息佇列和微服務,會面臨獨特的挑戰。本文說明用於在 Spanner 佇列中執行「只處理一次」和「最多確認一次」處理作業的核心概念和設計模式。
核心概念和冪等性難題
「冪等」是指無論作業執行多少次,結果一律相同。建構訊息佇列或工作傳送系統時,有三種主要傳送方法可解決等冪性問題:
- At-least-once:保證訊息會傳送並處理。如果發生網路故障,系統可能會多次傳送及處理訊息。
- At-most-once:訊息最多只會傳送一次。系統會避免重複處理,但如果發生失敗,訊息可能會遺失或捨棄。
- 僅需處理一次:每則訊息只會處理一次,不會遺失或重複。
Spanner 佇列會自動提供至少傳送一次和最多確認一次功能。不過,您可以運用下列各節說明的設計模式和範例,以程式輔助方式減少重複處理和重新傳送作業。
未知的提交狀態問題
在單一機器資料庫中,交易不是成功就是失敗。在分散式雲端資料庫中,資料會複製到多個實體資料中心,因此可能會遺失交易。
應用程式處理訊息並提交確認 (例如 DELETE FROM MessageQueue WHERE message_id = '123' ASSERT_ROWS_MODIFIED 1) 時,要求會經過多個網路躍點:
[Your App] <--(gRPC)--> [Spanner API] <--(Google network)--> [Spanner Storage]
Spanner 儲存空間領導者收到提交內容後,會同步處理儲存空間副本。達成共識並將資料寫入磁碟後,交易就會在資料庫中成功修訂。
不過,資料寫入磁碟後,成功確認通知送達應用程式前,可能會發生暫時性網路故障 (例如 Proxy 重新啟動或網路中斷)。如果發生這種情況,應用程式會收到網路逾時錯誤 (DEADLINE_EXCEEDED 或 UNAVAILABLE)。
由於連線是頻外中斷,應用程式無法判斷連線是在提交成功前還是後中斷。這就是所謂的「不明提交狀態」問題。
自動重試可能造成的危險
如果應用程式攔截網路逾時錯誤,並重試完全相同的交易,但未進行重複資料刪除,則可能執行兩次商業邏輯。舉例來說,如果交易涉及向顧客的信用卡收費或運送商品,重試成功 (但未確認) 的交易會導致重複收費或出貨。
為保護使用者,官方 Google Cloud 用戶端程式庫絕不會 自動重試因不明提交錯誤而失敗的交易。這些函式會擲回明確的錯誤,提醒您交易結果不明,並讓應用程式程式碼使用等冪模式安全地處理重試。
僅需處理一次的實際策略
如要安全地重試交易,並減少重複處理作業和訊息重新傳送,請實作下列其中一種主要設計模式。
確認修改的資料列
如果所有商業邏輯 (例如更新其他資料表) 和佇列訊息確認都發生在單一 Spanner 交易中,您可以在 DELETE 陳述式中使用 ASSERT_ROWS_MODIFIED。這是最簡單的「只處理一次」策略,因為不需要另外建立重複資料刪除資料表。
在陳述式中附加 ASSERT_ROWS_MODIFIED 1,即可確保只有在刪除一則訊息時,確認才會成功。如果發生網路故障,且應用程式重試交易,佇列中就不會再有該訊息。重試會刪除 0 個資料列,且陳述式會因 OUT_OF_RANGE 而失敗。
OUT_OF_RANGE 錯誤是永久性的:資料列已消失,因此重新執行陳述式一律會修改 0 個資料列,並再次失敗。這也是陳述式層級的錯誤:只有 DELETE 會失敗。交易會保持開啟狀態,且您已在交易中進行的任何寫入作業仍會緩衝處理,如果繼續操作,系統就會提交這些作業。Spanner 不會為您中止或復原交易。如要取得預期的「正好一次」防護,請務必回溯交易:
- 讓錯誤傳播:如果您使用以執行器為基礎的 API (例如 Go 中的
ReadWriteTransaction),請讓OUT_OF_RANGE錯誤從交易函式傳播出去。用戶端程式庫會放棄交易並回溯,確保不會提交任何商業邏輯寫入作業。 - 請勿擷取並繼續:請勿在交易函式中擷取並繼續執行判斷提示錯誤。如果這麼做,Spanner 仍會提交其他陳述式,導致更新作業完全重複,而這正是這個模式要避免的情況。
- 手動復原:如果您手動管理交易 (例如使用 Go 中的
ReadWriteStmtBasedTransaction或 REST/gRPC API),則必須在斷言失敗時明確呼叫對應的復原方法。
交易輸出信箱
如果您的商業邏輯無法在單一 Spanner 交易中執行 (例如多步驟工作流程或跨多個系統的更新),或是訊息處理需要呼叫外部服務 (例如傳送簡訊或處理付款),您就無法以原子方式將商業邏輯與佇列確認項配對。
在這些情況下,請勿直接在 Spanner 交易區塊內呼叫外部 API 或執行非交易作業。如果 Spanner 重試或中止交易,應用程式可能會多次執行該商業邏輯。
應用程式應自行實作等冪策略,但您可以使用下列模式來減少重複作業:
快速作業 (在 10 秒的預設租用時間內完成):收到訊息時,執行商業邏輯或外部 API 呼叫。作業成功後,再確認訊息。如果作業失敗,或工作站在確認前當機,請勿確認訊息;租約到期後,Spanner 會自動傳送訊息以供重試。
長時間執行的作業 (超過 10 秒):收到訊息時確認,並在同一筆交易中,重新傳送排定在未來傳送的新訊息 (使用大於預估執行時間的傳送延遲時間)。或者,您也可以使用
RENEWLEASE_QUEUE_NAME()TVF 定期延長訊息租用期。如果商業邏輯或外部呼叫成功,請確認新加入佇列或延長的訊息。
多工工作階段冪等性
Spanner 工作階段可以多工處理。多工工作階段會追蹤共用工作階段的記憶體交易狀態。這樣一來,用戶端程式庫就能重新連線並更可靠地解決不明的提交狀態,防止發生用戶端網路故障。
不過,如果前端伺服器當機,多工工作階段就無法避免不明的提交結果。如果主機在工作階段交易表單的特定伺服器上,於提交後立即重新啟動,系統就會遺失記憶體內狀態,並在重新連線時傳回不明交易結果錯誤。為解決這項風險,請在本頁面實作等冪模式。
後續步驟
- 瞭解如何使用 Spanner 佇列,包括最佳做法和監控。
- 探索更多 Spanner 佇列情境和範例。
- 使用佇列的精細存取控管機制設定存取控管。