乾貨!如何平穩使用者無感知的完成系統重構升級

X先生發表於2020-09-08

前言

我們在實際開發系統的過程當中,很有可能會遇到需要進行系統重構升級的情況,需要重構的原因可能是之前的設計不合理,導致現在維護起來非常的困難,也有可能是現在的業務發展非常迅速,需要進行分庫分表了又或者之前用的是單機的本地的檔案儲存,現在需要用到統一的網路儲存。總而言之,就是當初的系統設計已經不符合現在發展需要了,需要進行重構和升級。

而這其中會可能會涉及到程式碼邏輯的變更,資料儲存的變更(如DB或者檔案儲存等)或者第三方介面的變更。在這樣一個新舊的切換過程當中,怎麼樣才能讓使用者無感知,平穩地進行過渡?

有人說可能說可以停服,然後遷資料,遷完後切新邏輯,然而先不說會有一段不可接受的不可用時間,就說在遷移過程中,我們如何保證能一次遷移成功呢?再退一步,就算資料遷移成功了,但是如果程式碼邏輯有漏洞,我們又該如何快速回退到舊版本呢?這可不單單是切回舊程式碼就好了,要知道這段時間可能產生了新版本的資料,這些新資料可也要遷回舊版本。

重構升級系統的過程可能會遇到這麼多問題,那我們有什麼辦法可以平穩且使用者無感知地完成系統升級嗎?今天就給大家提供一個通用的系統重構升級的框架。裡面很多具體的邏輯得按不同系統的實際情況來,但是整體思路卻是通用且可靠的。

場景模擬

我們先來模擬一個簡單的場景,並看看實際情況中應該如何操作。

假設我們一開始有個users表儲存學生資料,表結構以及一些資料如下:

idnameage
1張三18
2李四19
3王五17

後面隨著業務發展,我們需要記錄學生的語文成績,然後我們在users表加了score欄位,如下

idnameagescore
1張三1898
2李四1976
3王五1780

過一段時間我們發現又需要記錄數學分數了,後面還可能需要記錄英語分數等等。這時候不可能一次次加欄位,現有的表設計又極不滿足我們的需求,所以只好對現在的系統進行重構升級了。我們想用兩個表來存資料:

students

idnameage

mark表

idtypeuser_idscore

這時候我們會面臨幾個問題:

  • 程式碼邏輯的切換:包括增刪改查
  • 表結構的變更
  • 資料的遷移
  • 遷移過程中使用者無感知

如何來升級呢?

步驟

  1. 在舊程式碼的增刪改查的地方寫好新邏輯和建好新的表,但是一開始線上並不呼叫新邏輯和寫入新表,僅僅在測試環境呼叫和寫入,線上仍呼叫舊邏輯。如:

    if($is_dev){
      //新邏輯:如增刪改查students表和mark表
    }else{
      //舊邏輯:如增刪改查users表
    }
  2. 測試新邏輯沒問題了,線上同時雙寫新舊錶(包括增刪改),如:

    //新寫入邏輯:如增刪改students表和mark表
    //舊寫入邏輯:如增刪改users表
    
    
    if($is_dev){
      //新讀取邏輯:如查students表和mark表
    }else{
      //舊讀取邏輯:如查users表
    }
  3. 進行資料遷移,把原來users表的資料遷到students表和mark
  4. 然後讓系統執行一段時間,然後再對users表和students表、mark表的資料進行對賬,如果有資料不一致的情況,說明我們之前雙寫的時候有遺漏的地方,需要補全,如果沒有不一致,說明我們寫入的地方都已經對齊了,現在新舊資料是已經能一直保持一致了,那下面就是切讀的地方了。
  5. 把讀的地方改成只讀新的,如下

    //新寫入邏輯:如增刪改students表和mark表
    //舊寫入邏輯:如增刪改users表
    
    //新讀取邏輯:如查students表和mark表
  6. 系統執行一段時間,沒發現問題之後把寫的改成只寫新的,如下

    //新寫入邏輯:如增刪改students表和mark表
    
    //新讀取邏輯:如查students表和mark表

完成之後我們的系統就平穩的完成遷移了。

分析

整個過程可能看起來很繁瑣,沒關係,我們一步一步來分析其必要性。

  • 第一步是先寫好新邏輯,並進行充分的測試,這當然是必要的啦,算是升級前的準備
  • 第二步是我們升級的起始步驟了,我們需要雙寫資料。在程式碼釋出過程中,即使有的訪問到了新程式碼雙寫了,有的還是訪問舊程式碼單寫也沒關係,因為會有第三步資料遷移的過程。這一步和第三步是為了保證資料無感知遷移(不用停服遷移資料),保證遷移後資料的一致性。
  • 第四步是對賬過程,是為了找出我們寫入遺漏的地方,這是因為我們不能保證對於一個龐大的系統,你一次改造就能改到了所有的地方,所以留一段時間對寫入進行對賬是非常有必要的,有則改之。
  • 上面的第四步保證了我們新舊的資料已經是一致的了,這時候第五步我們就可以很放心的把我們讀從舊的地方改到讀新的地方了。
  • 第六步也是很重要的,我們要先讓系統平穩執行一段時間再切成單讀新表,因為這個過程中,如果我們發現系統新邏輯有問題,我們可以很多地切回讀舊邏輯,因為我們寫入還是雙寫的,舊資料還是有寫入,直接切回去是沒有問題的。這就避免了無法回滾,或者回滾後資料丟失的問題。
  • 注意第五步和第六步最好分開進行,也就是說不要一次就改單讀和單寫,不然在釋出程式碼的過程中,某些機器還是舊程式碼,這是使用者訪問系統的時候可能先訪問到新機器寫入了資料,然後又訪問到舊機器讀的是舊錶,這時候就會讀不到剛寫入的資料。這種臨界情況雖然極端,但是在大訪問量的基數還是可能出現的

總結

可以看到,上面的系統升級重構的思路是比較細緻的,但是確實是非常平穩,且不需要停服就能完成升級,即使系統非常的複雜,升級重構的邏輯和儲存結構大變樣也能適用。當然在實際過程中大家也可以根據實際情況(小系統小改動)進行一些步驟的合併或者縮短時間。

版權宣告

轉載請註明作者和文章出處
作者: X先生
https://segmentfault.com/a/1190000023924409

覺得不錯的話請幫忙收藏點贊~

相關文章