使用隊列實現彈性:構建一個永不跳過十億節拍的系統

已發表: 2018-12-21

Braze 代表其客戶每天處理數十億個事件,從而將數十億條高度集中的個性化消息發送給其最終用戶。 未能發送其中一條消息會產生後果,無論是錯過收據,還是更糟糕的是,錯過通知讓用戶知道他們的食物已準備好。 為了確保這些關鍵信息始終正確且始終準時,Braze 採用戰略方法來利用工作隊列。

什麼是作業隊列?

典型的作業隊列是一種架構模式,其中進程將計算作業提交到隊列,而其他進程實際執行這些作業。 這通常是一件好事——如果使用得當,它可以為您提供傳統的請求-響應範例無法獲得的並發度、可伸縮性和冗餘度。 許多工作人員可以在多個進程、多台機器甚至多個數據中心同時執行不同的作業,以實現峰值並發。 您可以分配某些工作節點來處理某些隊列並將特定作業發送到特定隊列,從而允許您根據需要擴展資源。 如果工作進程崩潰或數據中心離線,其他工作人員可以執行剩餘的作業。

雖然您當然可以應用這些原則並輕鬆地小規模運行作業排隊系統,但當您處理數十億個作業時,接縫開始顯現(甚至破裂)。 讓我們來看看 Braze 在每天處理數千個、數百萬乃至現在數十億個工作的過程中面臨的一些問題。

缺乏一致性是一個弱點

如果我們發送一條消息,但在記錄我們剛剛發送該消息的事實之前就崩潰了,會發生什麼?

這裡可能會出現幾種不同的不良結果。 首先,您可能會重新安排失敗的作業並再次發送消息。 那……不理想:沒有人願意兩次收到相同的東西。 相反,請考慮根本不重新安排它。 在這種情況下,我們的內部會計將不正確,因此歸因、轉換和其他各種事情都不會正確向前發展。

我們如何解決這個問題? 在編寫工作定義時,我們會認真考慮冪等性和重試行為。

當您談論隊列時,冪等性意味著單個作業可以在任意點終止,重新排隊的作業將全部重新運行,最終結果將與我們成功運行該作業一樣時間。 這與我們選擇的重試行為密切相關——至少一次交付。 通過記住我們所有的作業將至少運行一次,甚至可能多次,我們可以編寫冪等作業定義,即使在隨機失敗的情況下也能確保一致性。

回到我們的消息發送示例,我們如何使用這些概念來確保一致性? 在這種情況下,我們可能會將作業分成兩部分,第一部分發送消息並將第二部分入隊,第二部分寫入數據庫。 在這種情況下,我們可以根據需要重試任一作業——如果消息發送提供程序已關閉,或者內部會計數據庫已關閉,我們將適當地重試,直到成功!

好柵欄造就好鄰居

當 Global Gizmos 的數據庫關閉時,我們的示例公司 Consolidated Widgets 的數據處理會發生什麼情況?

在這種情況下,如果我們的至少一次交付策略正在發揮作用,我們預計 Global Gizmos 的所有數據處理作業都會一遍又一遍地重試,直到它們成功。 這很棒——即使他們的數據庫關閉,我們也不會丟失任何數據。 然而,對於 Consolidated Widgets,它可能不是那麼好:如果工作人員不斷地重試和失敗,他們可能太忙而無法及時處理 Consolidated Widgets 的工作。

我們可以通過使用精心選擇的隊列名稱並根據需要暫停某些隊列來解決此問題。 有了這個在我們的工具帶中,我們可以以外科手術的方式減輕基礎設施的壓力。 在我們上面的場景中,一旦我們知道 Global Gizmos 的數據庫已關閉,我們可以暫停他們的數據處理隊列,直到我們知道它已備份,以確保一個特定的中斷不會影響任何其他客戶!

等待是痛苦的

如果 Consolidated Widgets 和 Global Gizmos 分別向 5000 萬用戶發送電子郵件活動,間隔 5 分鐘會怎樣? 誰先走?

簡單的作業排隊系統有一個簡單的“工作”隊列,工人從中提取作業。 一旦你有各種各樣的不同作業和作業類型,你可能會繼續擁有多種類型的隊列,每個隊列都有不同的優先級或從這些隊列中拉出的工人類型。 在這種情況下,我們有各種簡單的隊列用於數據處理、消息傳遞和各種維護任務。

快進到當您每天發送數十億條個性化消息時,一個“消息”隊列不會減少它——當隊列變得非常大時會發生什麼,就像我們上面的例子一樣? 我們是否優先考慮最先到達的工作?

我們的動態排隊系統試圖解決一種稱為作業飢餓的現象,即準備執行的作業在執行之前等待很長時間,通常是因為某種優先級。 在一個簡單的“消息”隊列中,優先級只是作業進入隊列的時間,這意味著添加到大隊列末尾的作業最終可能會等待很長時間。

當我們對一個活動及其所有消息進行排隊時,我們不是將作業添加到一個大的“消息傳遞”隊列中,而是為這個活動創建一個全新的隊列,並帶有一個特殊的名稱,以便我們知道它是什麼並且如何找到它。 將作業添加到隊列後,我們獲取“動態隊列”列表並將這個新隊列名稱添加到末尾。

通過採用這種策略,我們可以指示工作人員從“動態隊列”列表中獲取動態隊列的名稱,然後處理該特定隊列上的所有作業。 這使我們能夠確保盡可能快地發送消息,並確保我們所有的客戶都得到同等的重視。

因此,這還有其他好處,例如更高的緩存命中率和更少的數據庫連接,因為特定工作人員的工作位置增加了。 每個人都贏了!

始終有一個備份計劃

當數據庫關閉、一些隊列暫停並且作業隊列開始填滿時會發生什麼?

有時,重要的基礎設施只會在你身上死掉。 我們有備用設備和備份,但提升備份基礎設施所需的時間幾乎永遠不會為零。 在整個應用程序基礎架構中擁有多層隊列對於減輕這些類型事件的影響非常有幫助。

我們採用的一種策略是在設備本身上排隊。 數以百萬計的設備具有使用 Braze SDK 的不同應用程序,在這些應用程序中,我們利用隊列將數據發送到我們的 API。

當我們的 SDK 提交該數據並由於某種原因失敗時,SDK 會使用指數退避算法排隊重試,直到成功。 這種策略將基礎設施或代碼故障的影響降至最低,因為設備將簡單地將自己的數據排隊並在一切恢復在線時將其發送給 Braze。

快速行動而不破壞事物

歸根結底,我們的目標是比其他任何人都更好地發送高度專注的個性化信息,這包括快速行動、保持彈性和做好一切。 作業隊列是 Braze 基礎架構的核心,因此我們一直在關注我們的表現,採用最佳實踐,並嘗試新策略和先進技術,以成為遊戲中的佼佼者。

如果營銷自動化領域中這種類型的高性能、低延遲系統工程讓您興奮,那麼您一定要查看我們的工作板!