Spotify是怎樣從Postgres切換至Cassandra的?

jopen 9年前發布 | 11K 次閱讀 Cassandra
 

2015年5月,聞名全球的在線音樂平臺Spotify將他們的存儲系統從Postgres升級至Cassandra,整個過程中完全沒有停機時 間。Spotify為何要升級他們的存儲系統,促進他們升級的導火線是什么,他們如何做到無停機時間的在線升級,以及在升級過程中遇到了哪些困難?負責 Spotify登錄服務的團隊成員Marcus Vesterlund在博客中介紹了這次升級的整個過程。

介紹

Spotify的所有用戶信息目前已經從Postgres遷移至Cassandra數據庫,最后的切換過程是在5月11日完成的。作為負責Spotify用戶登錄功能的團隊,我們希望讓讀者了解一下我們所做的努力。

Spotify是怎樣從Postgres切換至Cassandra的?

PostgreSQL贊歌

僅僅去年,Spotify的新增活躍用戶就超過了3千5百萬(詳見20 Million Reasons to Say Thanks一文)。全部用戶的詳細信息都保存在user數據庫中,包括用戶名、國家及郵件等等。用戶每次登錄時都必須查詢該數據庫,同樣,每次創建新的 用戶、升級為Premium用戶、接受某個許可或是連接到非死book時,也都要訪問這個user數據庫。這意味著user數據庫非常繁忙,它擔負著 整個Spotify基礎設施中的核心功能。

我們當時所采用的Postgres數據庫是值得信任的,它已經為我們服務了許多年,但如今它所處理的數據量比設計時的目標已經高出了幾倍,并且數據集的增長速度每天都在繼續上升。

對這樣的核心基礎設施進行改動是一件令人畏懼的任務,但同時,我們確實不清楚Postgres還能堅持運行多久。

單點故障

我們的Postgres環境還受到另外一個問題的困擾:雖然讀操作分布在所有的數據中心,但所有的寫操作都發生在位于倫敦的一臺孤零零的機器上。 對此我們的一位網絡工程師Loke Berne有一句名言:“從網絡運維的角度來說,如果能把那個恐怖的機柜徹底干掉,那實在太美妙了。”他所指的機柜正是包含了user數據庫寫入功能的主 節點。讓僅僅這么一臺機器作為user數據庫的主節點可不是什么有趣的事,對于用戶信息的所有更新,例如新創建的帳號、或者是讓7千5百萬活躍用戶升級為 Premium用戶,這些操作都是由這臺可憐的機器所處理的。一旦這臺機器產生故障,以上這些操作都會失敗,必須等到某臺熱備機器升級為主節點,并將訪問 量發送到新的主節點為止。

Postgres的淘汰已不可避免

作為負責登錄服務的團隊,我們深知現有的解決方案將無法跟上用戶的增長速度。它就像是一匹年邁的老馬,雖然也許能夠捱過這個冬天,但今后的日子將越來越難捱。我們心里都明白,現在是時候讓它停下了,但扣住扳機的手卻在不停地顫抖。

Spotify是怎樣從Postgres切換至Cassandra的?

一切都開始于鯊魚的一咬

2013年9月,連接于倫敦與阿什伯恩數據中心之間的大西洋然斷開了。有傳言說是鯊魚咬斷了光纜,不論事實真相如何,它所造成的結果是我們的新用戶數量在一周內大幅下降。如果我們能夠在網絡的另一邊創建新用戶,那么這次網絡異常所帶來的問題就不會那么嚴重了。

我們其實已經知道倫敦的單點故障是有問題的,但直到9月份的那一周,我們才清楚地知道,這種設計上的失敗不僅僅停留在理論上而已。單點故障給我們 造成了實實在在的商業損失,這種損失能夠很容易地換算成歐元和美金。我們很久之前就開始考慮以Cassandra作為解決方案,但一直沒有機會專注于這方 面的工作。而現在情況已經很明顯了,我們必須找到一種新的解決方案。

Spotify是怎樣從Postgres切換至Cassandra的?

為行駛中的汽車更換引擎

“如果某件事你做的足夠出色,那么人們甚至不會感覺到它。”

—— 動畫“飛出個未來”其中關于“Godfellas”的一集中上帝的實體的名言。

這條格言非常適用于基礎設施的改動,雖然這一目標并非總是可行的,但確實是我們的努力方向。數據的遷移是個非常棘手的任務,但我們不希望在遷移時 停下整個系統,這會影響用戶登錄以及新用戶的創建。因此,我們必須進行一次無縫的切換。這就意味著我們需要讓這兩個存儲系統同時運行一段時間,以 Postgres作為主存儲機制,同時隱蔽地運行Cassandra存儲系統。所謂隱蔽就是指并行地進行實際請求的處理,但忽略其結果。

這種方式能夠帶來多種益處:

我們能夠確保新的存儲方案其能力足以處理現有的負載,對于Postgres的能力需求我們已經很了解了,但Cassandra是一種不同的系統,因此必須對其進行實際評估。

與Cassandra存儲相關的代碼也在實際運行中,因此我們能夠在最終切換之前找到所有的bug,以及健壯性和可伸縮性方面的問題。

因為我們隱蔽地運行著Cassandra,因此即使它發生了故障也不要緊。主進程將記錄下這次錯誤的信息,但忽略它的錯誤結果。

我們可以在新舊存儲系統之間保持數據的同步。

遷移現有賬號

除了隱蔽地處理寫入操作,我們還必須對所有用戶進行遷移。為此,我們通過一個后臺作業,讓它將Postgres中的所有用戶逐個復制到Cassandra中。我們必需確保將競態條件最小化,因為隱蔽的寫入操作有可能與帳號的遷移過程同時進行。

由于Postgres工作方式的限制(在進行一個長時間運行的查詢時,復制過程會中止),我們不得不以一種特殊的方式進行遷移。我們必須保證在運行遷移腳本之前,所有的寫入操作,包括新建帳號與帳號更新,都已經進行了適當的隱蔽式處理。

對一個只讀的從節點進行一個長時間的查詢,以得到大量的用戶名。

對于每個用戶名: A. 如果該用戶名已經存在于Cassandra中,那么就無需進行遷移。還記得吧,我們假設隱蔽的寫入操作有接近100%的覆蓋率。 B. 如果該用戶名不存在,那么就準備進行遷移。 C. 對另一個只讀的從節點進行查詢,以獲得對應這個用戶名的用戶數據(請記住,在長時間的查詢過程中,復制過程會中止。因此,如果我們還是對第一個從節點進行 查詢,萬一用戶在這個長時間運行的查詢啟動后修改了個人信息,那我們就有可能會獲取到過期的數據)。 D. 從第二個從節點中獲取數據,并插入Cassandra數據庫(從這一刻開始,如果這個用戶產生了任何數據變更,都會同時反映在Postgres與 Cassandra數據庫中)。

Spotify是怎樣從Postgres切換至Cassandra的?

正確性驗證

我們還需要一個腳本以驗證這兩個存儲系統是否已經完全同步了。這個腳本也會以類似的方式逐個處理每個用戶,從兩個存儲系統中同時獲取用戶數據并進 行對比。它還能夠為我們生成有用的統計數據,告訴我們比較結果的差別,以及差別的頻度。這種方式已經證實對于bug的排查非常有幫助。

開始切換

在進行實際切換過程中,Spotify后臺所采用的微服務架構幫了我們一個大忙。它的思想是將對user數據庫的實際調用封裝在一個RESTful的服務中,這種方式確保只有一個服務了解存儲層的細節。實際的切換過程其實僅包括:

將配置中的主節點與隱蔽式節點的角色進行切換。

確保新的配置在所有服務機器上都已生效。

同時重啟所有服務的實例。

同時重啟所有服務的實例是很重要的一步,它能夠將沖突的風險降至最低。如果有部分服務將Postgres當作主節點,而另一部分服務將Cassandra當作主節點,會發生什么情況呢?我們可能會在新建帳號時產生沖突,導致同一個用戶名指向不同的帳號!

同時重啟所有服務實例也有一些負面影響。重啟過程大概會持續幾秒鐘,這段時間內無法創建帳號或進行登錄操作。好在我們的客戶都很聰明,他們會自己嘗試重新登錄。

這種切換方式也讓我們產生了一個良好的回滾計劃,一旦出了什么問題,我們就能夠以同樣的方式簡單地撤消,重新使用Postgres作為主節點。

實際的切換結果如何?

首先,我們要確保處于一個良好的狀態,通過執行驗證腳本將當前的不一致數量降至最低。然后我們啟動了切換過程,并注視著日志與圖形信息。整個過程 相當平靜,我們并沒有發現什么令人振奮或吃驚的事。事實上,這是我經歷過的最乏味的一次部署了。接下來唯一要做的事就是手動地修復一些不一致的地方。

這一路所遇到的各種阻礙

在遷移至Cassandra的過程中,我們遇到了許多阻礙。其中有半數我已經記不太清了,但我還是想說明一下有哪些最大的阻礙是我們所必須克服的。

Paxos算法出錯?

Cassandra 1.2版本中引入了一個別出心裁的的特性,名為LWT“輕量級事務”,或者叫“條件式插入”。它的本質在于能夠確保鍵的唯一性,這一點對于Spotify 來說相當重要,它可以保證一個用戶名僅屬于一個用戶。我們可不想在兩個用戶同時創建帳號時允許沖突或競態,然后決定讓他們共享同一個用戶名。

因此,為了避免沖突,我們決定使用LWT,它的底層使用了一個著名的分布式一致性算法Paxos。它在Cassandra上的實現需要為復制節點設置一個仲裁(quorum),并且需要經過四個來回的通信過程,這種操作的代價相當之高。

我們進行了一次基準測試,并得出了一個結論:在創建新帳號時使用Paxos算法的代價不算太高。但在生產環境中實際測試時,卻發現有大量的創建帳號操作都失敗了,因為Paxos認為這些用戶名與帳號已經存在。

我們為此提交了一個錯誤報告CASSANDRA-9086,并等待官方的回復。

而最終的回復表示,這是一種預期行為,我們可以在自己的服務代碼中處理這一問題。

Paxos需要求所有節點參與CAS操作?

這是個有趣的bug。正如我之前所說,Paxos算法要求設置仲裁節點以實現一致性。如果這一過程失敗,整個操作也會失敗,而后將返回一些額外的信息。比方說,你將得到能夠參與Paxos過程的節點數量。

我們注意到了一點,在帳號創建失敗時,錯誤消息中所顯示的數字是所有復制節點的數目,而不是作為仲裁的復制節點的數目。這其實是我們所使用的Cassandra版本中的一個bug(CASSANDRA-8640),隨后我們將整個集群進行升級,簡單地解決了這個問題。

Java驅動(撤銷了2.10.0版本中的JAVA-425變更)

我們使用了一個由Datastax創建的開源Cassandra客戶端,這家公司 雇用了大量的Cassandra貢獻者。經過幾個星期的運行,系統的負載也有所上升,此時我們注意到:從我們的服務到Cassandra的連接數量在下降,卻沒有重新建立起新的連接。

這個bug將影響真實環境中的訪問量,我們使用了異步的Java驅動API,而沒有使用分離的線程池。一旦無法建立新的連接,API就將開始阻 塞。最后我們終于觸及了并發的上限,連續幾個小時不停地收到系統的警報。所幸經過手動重啟后服務又能夠繼續運行了,同時我們也開始追蹤其根本原因。

最后證明我們所使用的客戶端版本中有一個新的回歸缺陷(由JAVA-425修改所造成),于是我們升級至最新的版本,其中撤消了JAVA-425這個修改,問題得以解決。

對備份的依賴

當遷移至Cassandra后,我們需要開始對Cassandra數據庫進行每日備份。這項任務之前是在Postgres數據庫上進行的,有許多 大數據批處理作業(用于進行個性化、業務分析等等)依賴于它。但Cassandra的備份與Postgres的備份有著細微的差別,而有些批處理作業無法 處理這種差別。這個問題至今也沒有完全解決,但我們正在積極地處理它。

總結

地球人都知道,軟件項目所花的時間總是比預計的要長,我們的遷移項目也不例外。但我們確實做到在整個切換過程幾乎沒有什么閃失。通常來說,如果事 情太過順利,我們有時反而會認為一定出現了什么要命的錯誤。好在這一次是個例外,原因是我們在隱蔽式運行階段已經觸及了大量的問題,這對于終端用戶幾乎沒 有什么影響。我們也編寫了驗證腳本、跟蹤日志,并且不斷地查看各種圖表。因此在最終切換時,我們非常有信心不會遇到太大的問題。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!