1. 程式人生 > >如何在不停機的情況下,完成百萬級資料跨表遷移?

如何在不停機的情況下,完成百萬級資料跨表遷移?

技術團隊面臨的困難總是相似的:在業務發展到一定的時候,他們總是不得不重新設計資料模型,以此來支援更加複雜的功能。在生產環境中,這可能意味著要遷移幾百萬條活躍的資料,以及重構數以千行計的程式碼。

Stripe的使用者希望我們提供的API要具備可用性和一致性。這意味著在做遷移時,我們必須非常小心:儲存在我們系統中的資料要有非常準確的值,而且Stripe的服務必須時刻保證可用。

在這篇文章中,我們將分享我們是如何安全地完成了一次涉及上億資料量的大遷移經歷。

為什麼遷移總是這麼難?

規模

Stripe有上億規模的訂閱資料。對於我們的生產資料庫來說,做一次與所有這些資料都相關的大型遷移就意味著非常非常多的工作。想象一下,假如以一種順序的方式,每遷移一條訂閱資料要一秒鐘,那要完成上億條資料的遷移就要耗時超過三年。

不能停機

Stripe上的業務一直在執行。我們升級所有東西都是不停機操作的,而不是可以在某個計劃好的維護視窗內更新。因為在遷移過程中我們不能簡單地中止訂閱服務,所以我們必須100%地在所有服務都線上的情況下完成遷移操作。

資料準確

我們很多的業務都用到了訂閱資料表。如果我們想要一次改動訂閱服務的幾千行程式碼的話,那可以肯定地說我們一定會遺漏某些特殊場景。我們必須確保每個服務都能持續地操作準確的資料。

線上遷移的模式

把數以百萬計的資料從一張資料庫表遷移到另一張中,這很困難,但對於許多公司來說這又是不得不做的事。

在做類似的大型遷移時,有種大家非常容易接受的四步雙寫模式。這裡是具體的步驟。

1.向舊錶和新表雙重寫入,以保持它們之間資料的同步;

2.把程式碼庫中所有讀資料的操作都指向新表;

3.把程式碼庫中所有寫資料的操作都指向新表;

4.把依賴舊資料模型的舊資料刪掉。

我們的遷移案例

Stripe的訂閱功能幫助像DigitalOcean和Squarespace這樣的客戶構建和管理他們使用者的計費賬單。在過去的幾年裡,我們持續不斷地增加了許多功能,來支援他們越來越複雜的計費模型,比如多重訂閱、試用、優惠券和發票等。

在最開始時,每個Customer物件最多隻會有一條訂閱資料。所以我們的客戶資料都儲存成了單條記錄。因為使用者和訂閱之間的對映關係非常直接,所以訂閱資訊就和使用者資料儲存在了一起。

後來,我們發現有些客戶希望他們建立的Customer物件可以對應多條訂閱資料。於是我們決定把服務於單次訂閱的單條訂閱資料升級一下,換成一個訂閱陣列,以此來儲存多條有效的訂閱資料。

在繼續新增新功能的時候,這樣的資料模型就出問題了。每一次對使用者的訂閱資訊的改動都意味著要更新整條使用者記錄,以及查詢使用者資料的與訂閱相關的檢索語句。於是我們決定把這些訂閱資訊單獨儲存起來。

資料模型
我們重新設計的資料模型把訂閱資訊移到了它們自己的表裡。

複習一下,我們的四步遷移流程為:

  1. 向舊錶和新表雙重寫入,以保持它們之間資料的同步;
  2. 把程式碼庫中所有讀資料的操作都指向新表;
  3. 把程式碼庫中所有寫資料的操作都指向新表;
  4. 把依賴舊資料模型的舊資料刪掉

接下來我們看看這理論上的四個階段在我們的實際專案中是怎樣實施的。

第一部分:雙重寫入

我們在遷移之前先建立了一張新的資料表。第一步就是開啟複製新寫入的資料,這樣它就可以寫到新舊兩張表裡了。然後我們再把新資料表中缺失的資料慢慢地補充過來,這樣新舊兩張表裡的資料就完全一致了。

資料表

所有新的寫入都要更新兩張資料表。

在這個案例中,我們會把所有新生成的訂閱資訊都同時寫入使用者表和訂閱表。在開始雙重寫入兩張表之前,一定要認真考慮一下這一份額外的寫入操作給生產庫的效能帶來的影響。有種減輕效能影響的方法就是慢慢地增大開啟複製的資料量,這同時一定要仔細地盯著各項運營指標。

到了這一步,所有新建立的資料就都同時存在於新舊兩張表裡了,而比較舊的資料只儲存在舊錶中。於是我們可以以一種緩慢的模式開始拷貝已有的訂閱資訊:每當有資料被更新的時候,就自動地把它們也拷到新的表中。這種方法讓我們可以開始增量地遷移已有的訂閱資料。

最終,我們會把所有已有的使用者訂閱資訊都補充到新的訂閱表中去。

訂閱表

我們會把所有已有的使用者訂閱資訊都補充到新的訂閱表中去。

在生產資料庫裡補充新資料表的操作,代價最大的部分其實就是要找出所有需要遷移的資料而已。通過檢索資料庫來找出所有這樣的資料需要檢索生產庫很多次,這會花費很多時間。幸運的是,我們可以把這個代價轉用一個離線的方式完成,因此對生產庫就毫無影響了。我們會為資料生成快照,並上傳到Hadoop叢集中,然後就可以用MapReduce的方法來快速地以離線、並行、分散式的方式處理資料了。

我們用Scalding來管理我們的MapReduce任務。Scalding是一個用Scala寫成的非常有用的庫,用它來寫MapReduce任務非常容(寫一個簡單任務的話連十行程式碼都不用)。在這個案例中,我們用Scalding來找出所有的訂閱資料。具體步驟如下:

  • 寫個Scalding任務來生成所有需要遷移的訂閱資料的ID列表;
  • 做一次大型的、多執行緒的遷移操作,來並行地把所有需要遷移的訂閱資料快速拷貝過去;
  • 當遷移結束之後,再執行一次Scalding任務,確保所有舊訂閱表中的訂閱資料都遷移到了新表裡,沒有遺漏;

第二部分:切換所有的讀操作

現在新舊兩張資料表中的資料都處於同步狀態了,下一步就是把所有的讀操作都遷移到新資料表上來。

訂閱表

到這一步時,所有的讀操作都仍然在使用舊的使用者表:我們要切換到新的訂閱表上來。

我們要很確信可以從新的訂閱表中正常讀出資料,這也意味著我們的訂閱資料必須是一致的。我們用GitHub的Scientist來幫我們做驗證。Scientist是一個Ruby庫,可以讓我們執行測試,比較兩段不同的程式碼的執行結果,如果在生產環境中兩種表述會產生不同的結果,它就會發出警告。有了Scientist,我們就可以實時地為不同的結果產生告警和獲得指標。萬一測試用的程式碼產生了錯誤也沒有關係,我們的程式的其它部分並不會受到影響。

我們會做下面的驗證:

  • 用Scientist去分別從訂閱表和使用者表中讀出資料;
  • 如果結果不同,就丟擲錯誤,提醒技術人員資料不一致;

Scientist

GitHub的Scientist讓我們可以同時從兩張表中讀出資料,並且比較結果。如果驗證通過,所有資料都能對得上,我們就可以從新表中讀入資料了。

我們的驗證很成功:所有的讀操作都使用新的訂閱表了。

第三部分:切換所有寫操作

接下來,我們要把所有寫操作切換到新的資料表上來。我們的目標是漸進式地推進這些變動,因此我們要採用非常細緻的戰術。

到目前為止,我們一直在向舊資料表中寫入資料,並複製到新表中:

資料

現在我們想調換這個順序:向新資料表中寫入資料,並且同步到舊資料表中去。通過保持這兩張資料表之間的資料一致,我們就可以不斷地做增量更新,並且細緻地觀察每次改動的影響。

把所有處理訂閱資料的程式碼都重構掉,這一塊應該是整個遷移過程中最有挑戰性的了。Stripe處理訂閱操作的邏輯分佈在若干個服務的幾千行程式碼中。

成功重構的關鍵就在於我們的漸進式流程:我們會盡可能地把資料處理邏輯限制到最小的範圍內,這樣我們就可以很小心地應用每一次改動。在每個階段裡,我們的新舊兩張表中的資料都會保持一致。

對於每一處程式碼邏輯,我們都會用全面的方法來保證我們的改動是安全的。我們不能簡單地用新資料替換舊資料:每一塊邏輯都必須經過審重地考慮。不管我們漏掉了哪種特殊情況,都有可能會導致最終的資料不一致。幸運的是,我們可以在整個過程中不斷地執行Scientist測試來提醒我們哪裡可能會有不一致的情況發生。

我們簡化了的新寫入方式大概是這樣的:

到最後我們加入邏輯,如果有任何呼叫這樣過期的訂閱資料的情況發生,我們都會強制丟擲一個錯誤。這樣我們就可以保證再也沒有程式碼會用到它了。

訂閱資料

第四部分:刪除舊資料

我們最後也是最有成就感的一步,就是把寫入舊資料表的程式碼刪掉,最後再把舊資料表刪掉。

當我們確認再沒有程式碼依賴已被淘汰的舊訂閱資料模型時,我們就再也不用寫入舊資料表中了:

做了這些改動之後,我們的程式碼就再也不用使用舊資料表了,新的資料表就成了唯一的資料來源。

然後我們就可以刪除掉我們的使用者物件中的所有訂閱資料了,並且我們會慢慢地漸進式地做刪除操作。首先每當我們載入訂閱資料時,我們都會自動地清空資料,最後會再執行一次Scalding任務以及遷移操作,來找出所有遺漏的未被刪除的資料。最終我們會得到期望的資料模型:

資料模型

最後的結論

在遷移的同時還要保證Stripe的API是一致的,這事很複雜。我們有下面這些經驗可以和大家分享:

  • 我們總結出了四階段遷移策略,這讓我們可以在生產環境中沒有任何停機時間就可以完成資料切換操作。
  • 我們採用Hadoop用離線的方式進行了資料處理,這讓我們可以用MapReduce並行地處理大量資料,而不是依靠對生產庫進行代價昂貴的檢索操作。
  • 我們所有的改動都是漸進式的。每一次我們改動的程式碼量都絕對不會超過幾百行。
  • 我們所有的改動都是高度透明和可觀測的。哪怕生產環境中有一條資料不一致,Scientist測試都會立刻向我們告警。通過這種辦法,我們可以確信我們的遷移操作是安全的。

我們在Stripe已經做過許多次線上遷移了,經過實踐檢驗這些經驗非常有效。希望別的團隊在做大規模資料遷移時,我們的這些經驗也可以對他們有所幫助。

作者:Jacqueline Xu

文章來自微信公眾號:聊聊架構