《如何在不停機的情況下,完成百萬級數據跨表遷移?》要點:
本文介紹了如何在不停機的情況下,完成百萬級數據跨表遷移?,希望對您有用。如果有疑問,可以聯系我們。
技術團隊面臨的困難總是相似的:在業務發展到一定的時候,他們總是不得不重新設計數據模型,以此來支持更加復雜的功能.在生產環境中,這可能意味著要遷移幾百萬條活躍的數據,以及重構數以千行計的代碼.
Stripe的用戶希望我們提供的API要具備可用性和一致性.這意味著在做遷移時,我們必須非常小心:存儲在我們系統中的數據要有非常準確的值,而且Stripe的服務必須時刻保證可用.
在這篇文章中,我們將分享我們是如何安全地完成了一次涉及上億數據量的大遷移經歷.
Stripe有上億規模的訂閱數據.對于我們的生產數據庫來說,做一次與所有這些數據都相關的大型遷移就意味著非常非常多的工作.想象一下,假如以一種順序的方式,每遷移一條訂閱數據要一秒鐘,那要完成上億條數據的遷移就要耗時超過三年.
Stripe上的業務一直在運行.我們升級所有東西都是不停機操作的,而不是可以在某個計劃好的維護窗口內更新.因為在遷移過程中我們不能簡單地中止訂閱服務,所以我們必須100%地在所有服務都在線的情況下完成遷移操作.
我們很多的業務都用到了訂閱數據表.如果我們想要一次改動訂閱服務的幾千行代碼的話,那可以肯定地說我們一定會遺漏某些特殊場景.我們必須確保每個服務都能持續地操作準確的數據.
把數以百萬計的數據從一張數據庫表遷移到另一張中,這很困難,但對于許多公司來說這又是不得不做的事.
在做類似的大型遷移時,有種大家非常容易接受的四步雙寫模式.這里是具體的步驟.
1.向舊表和新表雙重寫入,以保持它們之間數據的同步;
2.把代碼庫中所有讀數據的操作都指向新表;
3.把代碼庫中所有寫數據的操作都指向新表;
4.把依賴舊數據模型的舊數據刪掉.
我們的遷移案例
Stripe的訂閱功能幫助像DigitalOcean和Squarespace這樣的客戶構建和管理他們用戶的計費賬單.在過去的幾年里,我們持續不斷地增加了許多功能,來支持他們越來越復雜的計費模型,比如多重訂閱、試用、優惠券和發票等.
在最開始時,每個Customer對象最多只會有一條訂閱數據.所以我們的客戶數據都保存成了單條記錄.因為用戶和訂閱之間的映射關系非常直接,所以訂閱信息就和用戶數據保存在了一起.
后來,我們發現有些客戶希望他們創建的Customer對象可以對應多條訂閱數據.于是我們決定把服務于單次訂閱的單條訂閱數據升級一下,換成一個訂閱數組,以此來保存多條有效的訂閱數據.
在繼續添加新功能的時候,這樣的數據模型就出問題了.每一次對用戶的訂閱信息的改動都意味著要更新整條用戶記錄,以及查詢用戶數據的與訂閱相關的檢索語句.于是我們決定把這些訂閱信息單獨保存起來.
我們重新設計的數據模型把訂閱信息移到了它們自己的表里.
復習一下,我們的四步遷移流程為:
接下來我們看看這理論上的四個階段在我們的實際項目中是怎樣實施的.
我們在遷移之前先創建了一張新的數據表.第一步就是開啟復制新寫入的數據,這樣它就可以寫到新舊兩張表里了.然后我們再把新數據表中缺失的數據慢慢地補充過來,這樣新舊兩張表里的數據就完全一致了.
所有新的寫入都要更新兩張數據表.
在這個案例中,我們會把所有新生成的訂閱信息都同時寫入用戶表和訂閱表.在開始雙重寫入兩張表之前,一定要認真考慮一下這一份額外的寫入操作給生產庫的性能帶來的影響.有種減輕性能影響的方法就是慢慢地增大開啟復制的數據量,這同時一定要仔細地盯著各項運營指標.
到了這一步,所有新創建的數據就都同時存在于新舊兩張表里了,而比較舊的數據只保存在舊表中.于是我們可以以一種緩慢的模式開始拷貝已有的訂閱信息:每當有數據被更新的時候,就自動地把它們也拷到新的表中.這種方法讓我們可以開始增量地遷移已有的訂閱數據.
最終,我們會把所有已有的用戶訂閱信息都補充到新的訂閱表中去.
我們會把所有已有的用戶訂閱信息都補充到新的訂閱表中去.
在生產數據庫里補充新數據表的操作,代價最大的部分其實就是要找出所有需要遷移的數據而已.通過檢索數據庫來找出所有這樣的數據需要檢索生產庫很多次,這會花費很多時間.幸運的是,我們可以把這個代價轉用一個離線的方式完成,因此對生產庫就毫無影響了.我們會為數據生成快照,并上傳到Hadoop集群中,然后就可以用MapReduce的方法來快速地以離線、并行、分布式的方式處理數據了.
我們用Scalding來管理我們的MapReduce任務.Scalding是一個用Scala寫成的非常有用的庫,用它來寫MapReduce任務非常容(寫一個簡單任務的話連十行代碼都不用).在這個案例中,我們用Scalding來找出所有的訂閱數據.具體步驟如下:
現在新舊兩張數據表中的數據都處于同步狀態了,下一步就是把所有的讀操作都遷移到新數據表上來.
到這一步時,所有的讀操作都仍然在使用舊的用戶表:我們要切換到新的訂閱表上來.
我們要很確信可以從新的訂閱表中正常讀出數據,這也意味著我們的訂閱數據必須是一致的.我們用GitHub的Scientist來幫我們做驗證.Scientist是一個Ruby庫,可以讓我們執行測試,比較兩段不同的代碼的執行結果,如果在生產環境中兩種表述會產生不同的結果,它就會發出警告.有了Scientist,我們就可以實時地為不同的結果產生告警和獲得指標.萬一測試用的代碼產生了錯誤也沒有關系,我們的程序的其它部分并不會受到影響.
我們會做下面的驗證:
GitHub的Scientist讓我們可以同時從兩張表中讀出數據,并且比較結果.如果驗證通過,所有數據都能對得上,我們就可以從新表中讀入數據了.
我們的驗證很成功:所有的讀操作都使用新的訂閱表了.
接下來,我們要把所有寫操作切換到新的數據表上來.我們的目標是漸進式地推進這些變動,因此我們要采用非常細致的戰術.
到目前為止,我們一直在向舊數據表中寫入數據,并復制到新表中:
現在我們想調換這個順序:向新數據表中寫入數據,并且同步到舊數據表中去.通過保持這兩張數據表之間的數據一致,我們就可以不斷地做增量更新,并且細致地觀察每次改動的影響.
把所有處理訂閱數據的代碼都重構掉,這一塊應該是整個遷移過程中最有挑戰性的了.Stripe處理訂閱操作的邏輯分布在若干個服務的幾千行代碼中.
成功重構的關鍵就在于我們的漸進式流程:我們會盡可能地把數據處理邏輯限制到最小的范圍內,這樣我們就可以很小心地應用每一次改動.在每個階段里,我們的新舊兩張表中的數據都會保持一致.
對于每一處代碼邏輯,我們都會用全面的方法來保證我們的改動是安全的.我們不能簡單地用新數據替換舊數據:每一塊邏輯都必須經過審重地考慮.不管我們漏掉了哪種特殊情況,都有可能會導致最終的數據不一致.幸運的是,我們可以在整個過程中不斷地運行Scientist測試來提醒我們哪里可能會有不一致的情況發生.
我們簡化了的新寫入方式大概是這樣的:
到最后我們加入邏輯,如果有任何調用這樣過期的訂閱數據的情況發生,我們都會強制拋出一個錯誤.這樣我們就可以保證再也沒有代碼會用到它了.
我們最后也是最有成就感的一步,就是把寫入舊數據表的代碼刪掉,最后再把舊數據表刪掉.
當我們確認再沒有代碼依賴已被淘汰的舊訂閱數據模型時,我們就再也不用寫入舊數據表中了:
做了這些改動之后,我們的代碼就再也不用使用舊數據表了,新的數據表就成了唯一的數據來源.
然后我們就可以刪除掉我們的用戶對象中的所有訂閱數據了,并且我們會慢慢地漸進式地做刪除操作.首先每當我們加載訂閱數據時,我們都會自動地清空數據,最后會再運行一次Scalding任務以及遷移操作,來找出所有遺漏的未被刪除的數據.最終我們會得到期望的數據模型:
在遷移的同時還要保證Stripe的API是一致的,這事很復雜.我們有下面這些經驗可以和大家分享:
我們在Stripe已經做過許多次在線遷移了,經過實踐檢驗這些經驗非常有效.希望別的團隊在做大規模數據遷移時,我們的這些經驗也可以對他們有所幫助.
作者:Jacqueline Xu
文章來自微信公眾號:聊聊架構
轉載請注明本頁網址:
http://www.snjht.com/jiaocheng/4220.html