Monzo使用Cassandra與微服務架構實現大規模支付運維過程中的事故與單點風險

banq發表於2019-09-04

系統出現嚴重的問題,馬上公開披露技術細節,而不是讓民間流言替代真相,這樣的分享值得點贊:

7月29日從大約13:10開始,你可能會遇到Monzo的一些問題:

可能無法:

  • 登入應用程式
  • 傳送和接收付款,或從ATM取款
  • 檢視應用中的準確餘額和交易
  • 通過應用內聊天或電話與我們聯絡

之所以發生這種情況是因為我們正在對我們的資料庫進行更改,而這些更改並沒有按計劃實施。我們知道,當涉及到你的錢時,任何形式的問題都是完全不可接受的。我們對此感到非常抱歉,我們致力於確保它不再發生。我們在那天結束時解決了大部分問題。從那以後,我們一直在調查究竟發生了什麼,並制定計劃以確保它不會再發生。

本著透明的精神,我們想從技術角度分析到底出了什麼問題,以及我們將來如何努力避免它。

Cassandra背景

我們使用Cassandra來儲存我們的資料,並擁有所有內容的多個副本。

Cassandra是一個開源的,高度可用的分散式資料儲存。我們使用Cassandra在多個伺服器之間傳播資料,同時仍將其作為我們服務的一個邏輯單元。

我們執行了21個伺服器(我們稱之為叢集)的集合。我們儲存的所有資料都在21臺伺服器中的三臺上覆制。這意味著如果一臺伺服器出現問題或我們需要進行有計劃的更改,則資料完全安全,並且仍可從其他兩臺伺服器獲得。

Cassandra使用一個稱為分割槽鍵的元素來決定21個叢集中的哪三個伺服器負責特定的資料。當我們想要讀取資料時,我們可以轉到叢集中的任何伺服器並請求特定分割槽金鑰的一段資料。我們的服務的所有讀取和寫入都會發生在仲裁選舉。這意味著在從Cassandra返回或寫入資料之前,至少有三分之二的伺服器需要參與確認該值。整個群集知道如何根據分割槽鍵轉換為實際儲存相同資料的三個伺服器。

我們正在擴大Cassandra以保持應用和卡支付順利執行

隨著越來越多的人開始使用Monzo,我們必須擴充套件Cassandra,以便它可以儲存所有資料並快速順利地提供服務。我們最後在2018年10月擴大了Cassandra的規模,並預計我們目前的產能將使我們滿足大約一年需求。

但在此期間,更多人開始使用Monzo,我們增加了執行的微服務數量,以支援Monzo應用程式中的所有新功能。

結果,在高峰負荷期間,我們的Cassandra叢集的執行速度接近我們想要的極限。儘管這並沒有影響我們的客戶,但我們知道如果我們很快沒有解決這個問題,我們就會開始看到服務請求的時間增加了。

對於我們的某些服務(特別是那些我們用於提供實時付款的服務),服務請求的時間越長意味著我們放慢了應用和卡支付的速度。當他們在排隊的前面為他們的購物買單時,沒有人願意等待很長時間!

因此,我們計劃增加群集的大小,以增加更多的計算容量並將負載分散到更多伺服器上。

發生在2019年7月29日的事情

這是當天發生的事件的時間表。所有時間都在2019年7月29日的英國夏令時間(BST)。

13:10我們通過向叢集新增六臺新伺服器來開始擴充套件Cassandra。我們有一個標誌集,我們認為這意味著新伺服器將啟動並加入叢集,但在我們向其傳輸資料之前保持非活動狀態。

我們確認使用資料庫和依賴它的服務(即伺服器和客戶端指標)的指標沒有影響。為此,我們會查詢錯誤的增加,延遲的變化或讀寫速率的任何偏差。所有這些指標都顯示正常且不受操作影響。

13:14我們的自動警報檢測到萬事達卡的問題,表明一小部分卡交易失敗。我們會告訴我們的付款團隊這個問題。

13:14我們收到客戶運營部門(COps)的報告,他們用來與客戶溝通的工具沒有按預期工作。這意味著我們無法通過應用聊天幫助客戶,讓與我們保持聯絡的客戶等待。

13:15我們宣佈了一個事件,我們的白天隨叫隨到的工程師對一小組工程師進行調查。

13:24發起資料庫更改的工程師注意到事件並加入調查。我們討論擴大規模的活動是否可能導致這個問題,但是因為一切看起來都很健康,所以可以忽略這種可能 沒有增加錯誤,讀取和寫入速率看起來沒有改變。

13:24我們的支付團隊在我們的一個Mastercard服務中發現了一個小程式碼錯誤,其中特定的執行路徑沒有正常處理錯誤情況。我們認為這是萬事達卡問題的原因,所以要開始修復。

13:29我們注意到我們的內部邊緣正在返回HTTP 404響應。

我們的內部優勢是我們編寫並用於訪問內部服務的服務(例如我們的客戶操作工具和我們的部署管道)。它會進行檢查以確保我們只允許訪問Monzo員工,並將請求轉發給相關地點。

這意味著我們的內部邊緣找不到它想要的目的地。

13:32我們收到COps的報告稱,有些客戶正在登出Android和iOS應用程式。

13:33 我們更新公共狀態頁面,讓我們的客戶瞭解這個問題。

13:33萬事達卡修復已準備好部署,但我們注意到我們的部署工具也失敗了。

13:39我們使用計劃的回退機制部署Mastercard修復程式。而且我們看到在信用卡交易立即改善成功

13:46我們發現內部邊緣沒有正確路由內部流量。除了身份驗證和授權之外,它還通過檢查請求並使用我們的配置服務來匹配我們的專用網路範圍來驗證它是“內部的”。

配置服務是Monzo微服務,為其他服務提供簡單的請求 - 響應(RPC)介面,以儲存和檢索表示配置的鍵值對。在內部,它使用Cassandra來儲存其資料。

我們得出結論,配置服務已關閉,或者我們的網路範圍已更改。我們很快排除後者並專注於服務。

13:48我們嘗試直接從配置服務中獲取資料,並意識到它為我們嘗試檢索的金鑰返回了404(未找到)響應。我們感到困惑,因為我們相信如果配置服務不起作用,它會產生比我們看到的更廣泛的影響。

13:53根據指標,我們看到對配置服務的成功讀取和寫入,這是令人驚訝的,因為我們剛剛看到它無法檢索資料。感覺就像我們看到了相互矛盾的證據。

13:57我們在配置服務中搜尋其他一些金鑰key,並意識到它們仍然存在。

14:00我們繞過配置服務介面直接查詢Cassandra。我們確認內部邊緣使用的金鑰實際上是從Cassandra中丟失的。

14:02此時我們認為我們正面臨著一些我們的部分資料無法獲得的事件,因此我們將注意力轉向Cassandra。

14:04儘管我們早先對Mastercard進行了修復,但我們仍然看不到它仍然沒有完全健康,支付團隊一直在努力。這意味著少量的卡支付仍然失敗

14:08我們查詢新的Cassandra伺服器是否取得了部分資料的所有權。鑑於我們對目前發生的事情有所瞭解,我們認為這是不可能的,但我們會繼續調查。

14:13我們對Cassandra中的某些資料發出查詢,並確認響應來自其中一個新伺服器。在這一點上,我們已經確認了我們認為不可能的事情,事實上已經發生了:

新伺服器加入了叢集,承擔了部分資料的負責(某些分割槽鍵以平衡負載),但尚未對其進行流式處理。這解釋了為什麼有些資料似乎缺失了。

14:18我們開始逐個淘汰新伺服器,以便我們將資料所有權安全地返回到原始群集伺服器。每個節點大約需要8-10分鐘才能安全移除。

14:28我們完全刪除了第一個節點,我們注意到正在籌集的404數量立即減少。

我們的內部客戶支援工具再次開始工作,因此我們可以使用應用內聊天回覆客戶

15:08我們刪除了最終的Cassandra節點,直接影響已經結束。對於大多數客戶,Monzo開始正常工作。

15:08→23:00我們繼續處理在六臺新伺服器主動提供讀寫操作時編寫的所有資料。我們通過結合外部儲存的重放事件和執行內部協調過程來檢查資料一致性。

23:00我們確認所有客戶現在都能夠獲得資金,正常使用Monzo,並在需要時聯絡客戶支援。

我們誤解了設定的行為

問題發生是因為我們期望新伺服器加入群集,並且在我們執行進一步操作之前保持不活動狀態。但事實上,當我們新增新的資料時,他們立即參與了資料的讀寫,儘管實際上沒有任何資料。誤解的來源是控制新伺服器行為方式的單一設定(或“標誌”)。

在Cassandra中,有一個標誌auto_bootstrap,用於配置伺服器啟動和加入現有叢集的方式。該標誌控制資料是否自動從現有的Cassandra伺服器流式傳輸到已加入群集的新伺服器。至關重要的是,它還控制查詢模式以繼續向舊伺服器提供讀取請求,直到較新的伺服器流式傳輸所有資料。

在大多數情況下,建議保留預設auto_bootstrap值為true。在此狀態下的標誌,伺服器以“非活動”狀態加入群集,分配一部分資料,並在資料讀取中保持非活動狀態,直到資料流過程結束。

但是,當我們在2018年10月的最後一次擴大完成時,我們為我們的生產環境設定了auto_bootstrap標誌false。我們這樣做是因為如果我們在叢集中丟失了一臺伺服器(例如,由於硬體故障)並且不得不更換它,我們就會從備份中恢復資料(這將顯著加快並減輕其餘部分的壓力)叢集)而不是使用具有資料的其他伺服器從頭開始重建它。

當auto_bootstrap設定為false,我們預期的六個新伺服器將被新增到叢集,商定資料,他們負責的分割槽,並保持不活動,直到我們開始在每個伺服器上重建/流過程中,一個接一個。

但事實並非如此。事實證明,除了資料流行為外,該標誌還控制新伺服器是加入活動狀態還是非活動狀態。一旦新伺服器就他們負責的資料分割槽達成一致,他們就會承擔全部責任而不需要任何基礎資料,並開始提供查詢。

由於某些資料是從尚未提供任何資料的新伺服器提供的,因此該資料似乎缺失。

因此,當某些客戶開啟應用程式時,無法找到本應存在的交易,這導致其帳戶餘額顯示不正確。

一旦問題得到解決,我們就能夠完全恢復資料並糾正任何問題。

為了阻止這種情況再次發生,我們正在做出一些改變

我們可以從這個問題中學到一些東西,並修復以確保它不會再次發生。

我們已經確定了運營Cassandra的知識差距

雖然我們經常在Cassandra上執行滾動重啟和伺服器升級等操作,但某些操作(如新增更多伺服器)不太常見。

我們在測試環境中測試了放大,但與生產的程度不同。

我們測試了一臺,而不是新增六臺伺服器。

為了獲得對生產部署的信心,我們線上提供了一臺新伺服器,並將其保持在初始的“無資料”狀態幾個小時。我們跨兩個叢集做了這個。

我們能夠確認對群集的其餘部分或環境的任何使用者沒有影響。在這一點上,我們很高興最初的加入行為auto_bootstrap與我們預期的一樣是良性的。因此,我們繼續將資料流式傳輸到新伺服器,並在整個過程中進行監控,並確認資料一致性或可用性沒有問題。

我們沒有考慮到的是法定人數(三個伺服器同意一個值)。只有一臺新伺服器,如果它完全加入叢集並且沒有任何資料,則無關緊要。在這種情況下,我們已經與群集中的其他兩個伺服器達成了協議。

但是當我們將六臺伺服器新增到生產中時,資料所有權將兩個或三個成員重新分配給新節點,這意味著我們沒有相同的保證,因為基礎資料沒有重新分配。

我們已經修復了錯誤的設定

我們已經修復了我們的使用auto_bootstrap。我們還審查並記錄了我們圍繞其他Cassandra設定的所有決策。這意味著我們擁有更廣泛的Runbook - 我們用於提供執行操作的逐步計劃的操作指南,例如放大或重新啟動Cassandra。這將有助於填補我們的知識空白,並確保知識傳播給我們所有的工程師。

延遲我們行動的另一個關鍵問題是缺乏將Cassandra作為問題主要原因的指標。因此,我們還考慮公開更多指標,併為“未找到行”等指標的強烈變化新增潛在警報。

我們將單個Cassandra叢集拆分為較小的叢集,以減少變更可能產生的影響

很長一段時間,我們為我們的所有服務執行一個Cassandra叢集。每個服務在叢集中都有自己的專用區域(稱為金鑰空間)。但是資料本身分佈在一組共享的底層伺服器上。

單一叢集方法對於工程師構建服務非常有利 - 他們不必擔心將服務放在哪個叢集上,我們只需要在操作上管理和監控一件事。但這種設計的缺點是,單一的變化會產生深遠的影響,就像我們在這個問題上看到的那樣。我們希望降低任何單項活動影響Monzo多個區域的可能性。

banq注:單點風險

使用一個叢集,查明故障源也更加困難。在這種情況下,我們從第一個警報開始差不多一個小時,直到我們將資訊拉到一個連貫的圖片中,突出了Cassandra的故障。對於更小且更受限制的系統配置,我們認為這將是一個不那麼複雜的問題。

從長遠來看,我們計劃將我們的單個大型叢集拆分為多個較小的叢集。這將大大降低像這樣的重複問題的可能性和影響,並使我們更安全地進行大規模操作。我們希望確保我們能夠做到正確; 這樣做不會帶來太多的操作複雜性,也不會減慢我們的工程師向客戶釋出新功能的速度。

​​​​​​​

相關文章