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

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"前言"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們在實際開發系統的過程當中,很有可能會遇到需要進行系統重構升級的情況,需要重構的原因可能是之前的設計不合理,導致現在維護起來非常的困難,也有可能是現在的業務發展非常迅速,需要進行分庫分表了又或者之前用的是單機的本地的文件存儲,現在需要用到統一的網絡存儲。總而言之,就是當初的系統設計已經不符合現在發展需要了,需要進行重構和升級。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而這其中會可能會涉及到代碼邏輯的變更,數據存儲的變更(如DB或者文件存儲等)或者第三方接口的變更。在這樣一個新舊的切換過程當中,怎麼樣才能讓用戶無感知,平穩地進行過渡?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有人說可能說可以停服,然後遷數據,遷完後切新邏輯,然而先不說會有一段不可接受的不可用時間,就說在遷移過程中,我們如何保證能一次遷移成功呢?再退一步,就算數據遷移成功了,但是如果代碼邏輯有漏洞,我們又該如何快速回退到舊版本呢?這可不單單是切回舊代碼就好了,要知道這段時間可能產生了新版本的數據,這些新數據可也要遷回舊版本。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"重構升級系統的過程可能會遇到這麼多問題,那我們有什麼辦法可以平穩且用戶無感知地完成系統升級嗎?今天就給大家提供一個通用的系統重構升級的框架。裏面很多具體的邏輯得按不同系統的實際情況來,但是整體思路卻是通用且可靠的。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"場景模擬"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們先來模擬一個簡單的場景,並看看實際情況中應該如何操作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設我們一開始有個"},{"type":"codeinline","content":[{"type":"text","text":"users"}]},{"type":"text","text":"表存儲學生數據,表結構以及一些數據如下:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a1/a1a461be332fb3459b1886562d71a7ef.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"後面隨着業務發展,我們需要記錄學生的語文成績,然後我們在"},{"type":"codeinline","content":[{"type":"text","text":"users"}]},{"type":"text","text":"表加了"},{"type":"codeinline","content":[{"type":"text","text":"score"}]},{"type":"text","text":"字段,如下"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2e/2e78c08a3b8b455e2e310773f477db78.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"過一段時間我們發現又需要記錄數學分數了,後面還可能需要記錄英語分數等等。這時候不可能一次次加字段,現有的表設計又極不滿足我們的需求,所以只好對現在的系統進行重構升級了。我們想用兩個表來存數據:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"students"}]},{"type":"text","text":"表"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/44/44df31b522f08f34721c0d7c25850ed7.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"mark表"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/29/299b6c950e8f65e11bf80383e97e23cb.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這時候我們會面臨幾個問題:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代碼邏輯的切換:包括增刪改查"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"表結構的變更"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據的遷移"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"遷移過程中用戶無感知"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如何來升級呢?"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"步驟"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"在舊代碼的增刪改查的地方寫好新邏輯和建好新的表,但是一開始線上並不調用新邏輯和寫入新表,僅僅在測試環境調用和寫入,線上仍調用舊邏輯。如:"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" ```"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" if($is_dev){"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" //新邏輯:如增刪改查students表和mark表"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" }else{"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" //舊邏輯:如增刪改查users表"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" ```"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"2","normalizeStart":"2"},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"測試新邏輯沒問題了,線上同時"},{"type":"text","marks":[{"type":"strong"}],"text":"雙寫"},{"type":"text","text":"新舊錶(包括增刪改),如:"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" ```php"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" //新寫入邏輯:如增刪改students表和mark表"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" //舊寫入邏輯:如增刪改users表"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" if($is_dev){"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" //新讀取邏輯:如查students表和mark表"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" }else{"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" //舊讀取邏輯:如查users表"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" ```"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"3","normalizeStart":"3"},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"進行數據遷移,把原來"},{"type":"codeinline","content":[{"type":"text","text":"users"}]},{"type":"text","text":"表的數據遷到"},{"type":"codeinline","content":[{"type":"text","text":"students"}]},{"type":"text","text":"表和"},{"type":"codeinline","content":[{"type":"text","text":"mark"}]},{"type":"text","text":"表"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"4","normalizeStart":"4"},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"然後讓系統運行一段時間,然後再對"},{"type":"codeinline","content":[{"type":"text","text":"users"}]},{"type":"text","text":"表和"},{"type":"codeinline","content":[{"type":"text","text":"students"}]},{"type":"text","text":"表、"},{"type":"codeinline","content":[{"type":"text","text":"mark"}]},{"type":"text","text":"表的數據進行"},{"type":"text","marks":[{"type":"strong"}],"text":"對賬"},{"type":"text","text":",如果有數據不一致的情況,說明我們之前雙寫的時候有遺漏的地方,需要補全,如果沒有不一致,說明我們寫入的地方都已經對齊了,現在新舊數據是已經能一直保持一致了,那下面就是切讀的地方了。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"5","normalizeStart":"5"},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"把讀的地方改成只讀新的,如下"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" ```php"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" //新寫入邏輯:如增刪改students表和mark表"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" //舊寫入邏輯:如增刪改users表"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" //新讀取邏輯:如查students表和mark表"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" ```"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"6","normalizeStart":"6"},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"系統運行一段時間,沒發現問題之後把寫的改成只寫新的,如下"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" ```php"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" //新寫入邏輯:如增刪改students表和mark表"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" //新讀取邏輯:如查students表和mark表"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" ```"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"完成之後我們的系統就平穩的完成遷移了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"整個過程可能看起來很繁瑣,沒關係,我們一步一步來分析其必要性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一步是先寫好新邏輯,並進行充分的測試,這當然是必要的啦,算是升級前的準備"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二步是我們升級的起始步驟了,我們需要雙寫數據。在代碼發佈過程中,即使有的訪問到了新代碼雙寫了,有的還是訪問舊代碼單寫也沒關係,因爲會有第三步數據遷移的過程。這一步和第三步是爲了保證數據無感知遷移(不用停服遷移數據),保證遷移後數據的一致性。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第四步是對賬過程,是爲了找出我們寫入遺漏的地方,這是因爲我們不能保證對於一個龐大的系統,你一次改造就能改到了所有的地方,所以留一段時間對寫入進行對賬是非常有必要的,有則改之。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面的第四步保證了我們新舊的數據已經是一致的了,這時候第五步我們就可以很放心的把我們讀從舊的地方改到讀新的地方了。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第六步也是很重要的,我們要先讓系統平穩運行一段時間再切成單讀新表,因爲這個過程中,如果我們發現系統新邏輯有問題,我們可以很多地切回讀舊邏輯,因爲我們寫入還是雙寫的,舊數據還是有寫入,直接切回去是沒有問題的。這就避免了無法回滾,或者回滾後數據丟失的問題。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注意第五步和第六步最好分開進行,也就是說不要一次就改單讀和單寫,不然在發佈代碼的過程中,某些機器還是舊代碼,這是用戶訪問系統的時候可能先訪問到新機器寫入了數據,然後又訪問到舊機器讀的是舊錶,這時候就會讀不到剛寫入的數據。這種臨界情況雖然極端,但是在大訪問量的基數還是可能出現的"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到,上面的系統升級重構的思路是比較細緻的,但是確實是非常平穩,且不需要停服就能完成升級,即使系統非常的複雜,升級重構的邏輯和存儲結構大變樣也能適用。當然在實際過程中大家也可以根據實際情況(小系統小改動)進行一些步驟的合併或者縮短時間。"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章