在咱們的工做中,常常會遇到系統或模塊重構工做,今天就來聊一聊我曾經經歷過的一次系統重構經歷。nginx
重構發生的背景是,原有的系統架構採用all-in-one的方式,隨着業務的快速發展,用戶訪問量急劇上升,系統請求流量成倍增加,陸續出現了各類問題。當時的系統架構的示意圖以下程序員
當時遇到的典型問題有數據庫
系統模塊耦合嚴重,訪問量上漲沒法快速擴容緩存
數據庫表混雜,定位不清。好比支付訂單和商品訂單在一張表,一個狀態字段表明兩種不一樣訂單的狀態流轉含義,常常會出現各類狀態異常單據。架構
複雜SQL和跨表join橫行,SQL慢查多,數據庫頻頻告警app
無服務和領域劃分,系統和接口耦合嚴重,常常是單點出問題,全系統宕機dom
接口響應慢,系統穩定性差,數據丟失、錯亂狀況常常出現分佈式
產品需求版本龐雜,業務需求場景多,業務邏輯分散,需求迭代速度慢性能
客訴問題高發,排查問題困難,研發疲於奔命在查問題的道路上學習
面對着這些問題,當時擺在眼前的方案有兩個
繼續按照原有系統迭代,但可能要付出更多的人力、精力來維持系統的穩定性和需求迭代速度
徹底重構系統,但須要投入必定的人力,而且可能會在短時間影響業務的需求迭代進展
考慮到產品會長期迭代,而眼前系統已經成爲巨大的瓶頸,所以決定對系統作徹底的重構。
當時我被領導安排做爲這個重構項目的負責人。但領導也提出了要求
公司業務在快速發展中,系統重構期間,需繼續保持業務需求的迭代速度,能夠適當增長人員
新系統設計和規劃,需考慮到3年後可能的用戶訪問量的上漲和數據量的上漲
新老系統切換期間,須要保證不影響用戶和業務方的正常使用,不出現數據的丟失和錯亂
任務既然已經肯定了,接下來就是考慮如何作的問題了。
系統重構是一個複雜的工程,而在一個業務高速發展的背景下作系統重構,無疑於給飛行中的飛機換引擎,須要考慮周全,計劃縝密,才能保證萬無一失。
針對面臨的問題和目標要求,在技術層面制定瞭如下幾點大的原則:
採用分佈式架構設計,將各個模塊系統徹底拆分出來,獨立部署迭代演進
數據庫模型徹底重構,原有的數據庫模型已經沒法支撐新的業務需求擴張,同時配合分佈式架構的改造落地
業務邏輯收歸,對涉及到的相關領域按照業務邏輯收口,統一服務接口
新老數據庫雙寫,保證系統穩定性和數據不丟失
新老系統並行提供服務,經過灰度控制流量切換,直至老系統下線
在大目標和技術方向肯定的狀況下,接下來就進入到實施階段。
考慮到系統中的核心場景和瓶頸都出如今訂單模塊,所以制定了分佈分階段實施的方案,第一步核心解決訂單相關功能的重構拆分,本文也將按照訂單系統的重構拆分來展開說明。
既然是系統級重構,首先須要對業務需求和產品功能進行梳理。
好在有產品的歷史文檔,加上經過線上產品的實時模擬驗證,可以將訂單相關的大體功能脈絡理清楚。
功能層面的需求梳理還沒法知足系統級重構的要求,須要更精確的梳理到接口級別,包括對訂單相關接口調用的上游模塊和訂單對其它下游模塊的調用,這樣才基本作到把訂單模塊的邊邊角角功能徹底覆蓋。
功能需求和接口層面的整理,爲數據庫表模型設計提供了大體的參考。
經過對已有產品功能和接口的分析,分析清楚訂單模塊提供的核心能力應該有哪些,和其它模塊的邊界是怎樣的,外部對訂單模塊的複雜調用需求有哪些,基於這幾點設計新的數據庫模型。這裏面有幾個關鍵的考慮:
大數據量的解決方案:分表。考慮到訂單數據量過大,原有的單一訂單主表已經沒法知足需求,所以將訂單主表按照用戶ID取模的方式分64張表,按照單表5000w數據的測算,基本能夠支撐將來3年內數據量的增加。按照用戶維度的分表方案,在單個用戶的訂單查詢場景下,經過數據庫單表就能夠完成。但除了按照用戶維度的查詢,還有按照時間、地域等維度的訂單查詢需求,考慮到繼續按照其它維度創建相應的分表方案太過冗餘,所以決定對其它的查詢能力經過ES構建搜索索引提供。
主鍵生成策略:分佈式ID自增。訂單表的主鍵,原來採用的是數據庫自增策略,分表後已再也不適合,借鑑twitter的snowflake方案,設計了分佈式的ID自增方案。
跨表查詢的解決方案:服務層聚合。原有的代碼中,有大量的跨表查詢,容易致使複雜SQL出現,嚴重影響數據庫性能。在新的數據庫表結構下,將表的職責劃分清楚後,再也不容許新的跨表查詢,涉及到跨表查詢的需求,經過在代碼層面拆分紅單表查詢再聚合的方式,解除跨表查詢帶來的問題。
新老模型雙寫:爲了保障系統的穩定性和不停機灰度流量驗證,設計開關來實現對新老模型進行雙寫,所以還須要將新老模型的相關表整理好對應關係,如表字段枚舉值不一樣帶來的映射等等。新老模型雙寫採用的方案也是經過程序處理,而非binlog等方式,主要考慮是爲了處理的靈活性和設置開關用於切換的可控性。
數據庫模型設計完成,接下來須要考慮到訂單模塊的架構設計方案。
根據對於需求的整理和理解、接口的梳理以及表模型的整理,大體能夠肯定的系統架構示意圖以下
這裏面有幾點須要說明:
首先,考慮到歷史版本App沒法強制要求全部用戶升級,所以須要在Nginx中將老版本的接口作重定向,轉發到新設計的接口服務層對應的接口上。
其次,對接口服務層作了拆分,因產品有不一樣的展示形態,包括App、Web管理後臺等,因不一樣用戶角色也有多個不一樣的App,所以設計接口服務層,將相關的用戶鑑權、數據加解密等統一收歸到這一層處理。
第三,設計業務邏輯層,將訂單相關的業務邏輯抽象到業務邏輯層,對外提供聚合封裝的訂單服務能力,如訂單詳情服務,訂單列表服務等。業務邏輯層須要調用訂單領域層的服務,還可能會調用到其它模塊的領域層服務作聚合,例如訂單詳情頁除了展示訂單的信息,還有商品相關信息、支付相關信息、配送相關信息,這些信息基本都在業務邏輯層作聚合處理。
第四,領域服務層,核心是本領域內數據庫表的操做封裝,這一層基本只作單個表的增刪改查。
最後,將訂單相關的庫從原有的單一庫中拆分出來,創建訂單庫。實際上訂單系統又分了多個領域,也可根據實際狀況將訂單相關的單一庫再作拆分細化。
以上的設計只是一個改造完後的方案。但真正在實施重構的時候,爲了保障線上系統能夠不停機切換,又分別做了相關的開關設計用於過渡階段的驗證。
階段一的過渡方案架構示意圖以下:
在階段一,有如下兩點設計
在接口服務層all-in-one-app應用中,設計開關,能夠控制all-in-one-app應用調用新的接口服務層,或繼續走原有的直接訪問數據庫的邏輯。一旦出現新服務、新的庫表模型有問題,經過開關直接切換回原有的調用鏈路中。
在領域服務層如oder-domain1-service、order-domain2-service、other-domain-service等應用中,設計開關,實現對原all-in-one庫和訂單庫的讀、寫開關。
第一階段上線後,正常的流程實現是
一、經過nginx將老的訂單相關接口,轉發到新的訂單接口服務層應用的相關接口,實現流量切換。
二、將all-in-one-app應用中的調用開關打開,切換到調用新的拆分過的相關業務邏輯層。
三、在領域服務層,將對all-in-one庫實現讀、寫,而對訂單庫實現只寫不讀。
這個階段主要驗證了整個服務和接口調用鏈路正常。當相關鏈路或環節出現問題,也能夠經過關閉對應開關快速切換回原有方案。
階段二的架構示意圖以下
通過階段一的驗證,基本能夠保證整個接口鏈路層面的邏輯正確,外部的調用方再也不感知接下來的改動變化。
階段二的核心是在內部的數據層面作驗證,保證落在新模型中的數據是正確無誤的。
這一階段沒有特別多的開發工做,主要操做是
一、在訂單領域層相關應用中,將對all-in-one庫的寫開關保留,讀開關關閉
二、在訂單領域層相關應用中,將對訂單庫的讀寫開關同時打開
這時整個調用鏈路和數據鏈路已經徹底實現了走新的接口服務和新的數據庫表。再經過產品功能層面驗證數據展示和產品流程是否正確,輔助老庫相關數據作對照,基本可以驗證整個系統的重構的正確與否。
這個階段若是相關鏈路或環節出現問題,能夠繼續經過開關的控制切回到原有的調用鏈路和數據鏈路。
階段二驗證經過後,後續還須要作一些收尾工做,包括去除雙寫代碼、去除代碼中的開關及歷史代碼邏輯等等。
整個架構方案肯定後,接下來的重點是制定重構項目的計劃,鎖定相關資源,肯定重構項目的各個里程碑節點。
項目計劃的制定,不只僅是關注訂單模塊自己的改造開發,還包括識別相關資源方和調用方,推進項目排期和落地。
在大部分的程序員認知中,只要本身系統沒有大問題,都不肯意作相關的改動,畢竟任何一點改造都會額外的工做量,也會對系統的穩定性有着未知的影響。另外業務方也可能會對重構有排斥,這時就須要搞定關鍵人物,將改造的利弊陳述清楚,有時甚至須要上升到更高的層級去推進。最終可以和相關方達成一致,肯定改造的時間計劃,提早鎖定對應的開發、測試資源,保障整個重構的順利進行。
開發階段的任務既包括重構相關的接口改造開發,還須要考慮新老庫表模型切換所作的兼容,包括新老庫表數據遷移兼容、消息隊列兼容、緩存兼容等。
在開發完成後,須要作新老庫表數據遷移的模擬演練,以驗證老的表數據導入到新庫表後,流程和展示不會出現問題。
系統級的重構改動,不可或缺的是全流程的測試驗證。
爲了保證測試的充分性,當時咱們採起了如下幾點關鍵措施:
一、經過已經沉澱和新增長的接口自動化用例,對大部分接口的響應和返回值作屢次的跑批驗證
二、經過測試人員不斷的交叉測試,對可能遺漏的業務場景驗證
三、經過將線上的流量複製重放,對新的接口進行邏輯驗證
四、經過預發環境的流量灰度,對全流程的業務作模擬驗證
系統重構在開發測試完成後,面臨的另一個重要問題是上線。
首先,制定詳細的上線計劃,將上線步驟事項按照前後順序所有羅列出來
其次,每一個上線步驟事項須要預估出操做的時間並明確責任人
第三,對任一環節可能出現的問題,提出假設並給出解決預案,防止上線中途出現問題因慌亂致使可能出現的異常。
最後,統一指揮,有序切換流量作灰度驗證,保障總體流程正常。
上線過程當中,藉助已有的監控系統,用於觀察系統、服務、接口等各項數據指標的變化狀況,判斷上線中的每一個環節是否有異常。
上線完成後,經過逐步的控制灰度流量佔比,驗證流程和數據是否正常,進而驗證整個重構是否成功完成。
在訂單模塊重構的過程當中,其它模塊也在改造和推進中,通過接近半年左右的時間,基本完成了對原有all-in-one服務的徹底拆分重構。
整個架構也在後續的迭代中不斷進行着新的重構和演進,包括在接口層前置設計網關接入層、訂單服務的細化拆分、ES對查詢場景的替換改造、業務邏輯層的中臺化演進等。
總結在整個重構過程當中的幾個關鍵步驟
分析目前系統的問題點,找到最重要最優先要突破的點
肯定重構所要達成的目標、方向及限制條件
肯定重構涉及到的核心技術方案及可行性
梳理重構所涉及到的需求、場景及相關上下游依賴方
設計明確和完善的技術方案
制定詳細的項目計劃,鎖定資源和里程碑節點並推動
全流程的測試驗證
詳細完備的上線計劃
不可或缺的灰度驗證
系統重構是一件耗時耗力的工做,但同時也是對自身綜合能力的一個巨大挑戰和鍛鍊,期間會遇到各類各樣新的問題。但正是經過這些真實的實戰,在不斷的重構中發現自身的能力瓶頸,去學習和成長。
若是你也有相關經歷和想法,也歡迎與我交流。