一次訂單系統重構實踐

程式設計師北哥發表於2020-11-21

在我們的工作中,經常會遇到系統或模組重構工作,今天就來聊一聊我曾經經歷過的一次系統重構經歷。

01 背景‍

重構發生的背景是,原有的系統架構採用all-in-one的方式,隨著業務的快速發展,使用者訪問量急劇上升,系統請求流量成倍增長,陸續出現了各種問題。當時的系統架構的示意圖如下

一次訂單系統重構實踐

02 痛點

當時遇到的典型問題有

  • 系統模組耦合嚴重,訪問量上漲無法快速擴容

  • 資料庫表混雜,定位不清。比如支付訂單和商品訂單在一張表,一個狀態欄位代表兩種不同訂單的狀態流轉含義,經常會出現各種狀態異常單據。

  • 複雜SQL和跨表join橫行,SQL慢查多,資料庫頻頻告警

  • 無服務和領域劃分,系統和介面耦合嚴重,經常是單點出問題,全系統當機

  • 介面響應慢,系統穩定性差,資料丟失、錯亂情況經常出現

  • 產品需求版本龐雜,業務需求場景多,業務邏輯分散,需求迭代速度慢

  • 客訴問題高發,排查問題困難,研發疲於奔命在查問題的道路上

面對著這些問題,當時擺在眼前的方案有兩個

  • 繼續按照原有系統迭代,但可能要付出更多的人力、精力來維持系統的穩定性和需求迭代速度

  • 完全重構系統,但需要投入一定的人力,並且可能會在短期影響業務的需求迭代進展

考慮到產品會長期迭代,而眼前系統已經成為巨大的瓶頸,因此決定對系統做完全的重構。

當時我被領導安排作為這個重構專案的負責人。但領導也提出了要求

  • 公司業務在快速發展中,系統重構期間,需繼續保持業務需求的迭代速度,可以適當增加人員

  • 新系統設計和規劃,需考慮到3年後可能的使用者訪問量的上漲和資料量的上漲

  • 新老系統切換期間,需要保證不影響使用者和業務方的正常使用,不出現資料的丟失和錯亂

任務既然已經確定了,接下來就是考慮如何做的問題了。

03 方案

系統重構是一個複雜的工程,而在一個業務高速發展的背景下做系統重構,無疑於給飛行中的飛機換引擎,需要考慮周全,計劃縝密,才能保證萬無一失。

針對面臨的問題和目標要求,在技術層面制定了以下幾點大的原則:

  • 採用分散式架構設計,將各個模組系統完全拆分出來,獨立部署迭代演進

  • 資料庫模型完全重構,原有的資料庫模型已經無法支撐新的業務需求擴張,同時配合分散式架構的改造落地

  • 業務邏輯收歸,對涉及到的相關領域按照業務邏輯收口,統一服務介面

  • 新老資料庫雙寫,保證系統穩定性和資料不丟失

  • 新老系統並行提供服務,通過灰度控制流量切換,直至老系統下線

04 實施

需求和介面的梳理

在大目標和技術方向確定的情況下,接下來就進入到實施階段。

考慮到系統中的核心場景和瓶頸都出現在訂單模組,因此制定了分佈分階段實施的方案,第一步核心解決訂單相關功能的重構拆分,本文也將按照訂單系統的重構拆分來展開說明。

既然是系統級重構,首先需要對業務需求和產品功能進行梳理。

好在有產品的歷史文件,加上通過線上產品的實時模擬驗證,能夠將訂單相關的大致功能脈絡理清楚。

功能層面的需求梳理還無法滿足系統級重構的要求,需要更精確的梳理到介面級別,包括對訂單相關介面呼叫的上游模組和訂單對其它下游模組的呼叫,這樣才基本做到把訂單模組的邊邊角角功能完全覆蓋。

功能需求和介面層面的整理,為資料庫表模型設計提供了大致的參考。

資料模型層面考量

通過對已有產品功能和介面的分析,分析清楚訂單模組提供的核心能力應該有哪些,和其它模組的邊界是怎樣的,外部對訂單模組的複雜呼叫需求有哪些,基於這幾點設計新的資料庫模型。這裡面有幾個關鍵的考慮:

  • 大資料量的解決方案:分表。考慮到訂單資料量過大,原有的單一訂單主表已經無法滿足需求,因此將訂單主表按照使用者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庫和訂單庫的讀、寫開關。

第一階段上線後,正常的流程實現是

1、通過nginx將老的訂單相關介面,轉發到新的訂單介面服務層應用的相關介面,實現流量切換。

2、將all-in-one-app應用中的呼叫開關開啟,切換到呼叫新的拆分過的相關業務邏輯層。

3、在領域服務層,將對all-in-one庫實現讀、寫,而對訂單庫實現只寫不讀。

這個階段主要驗證了整個服務和介面呼叫鏈路正常。當相關鏈路或環節出現問題,也可以通過關閉對應開關快速切換回原有方案。

階段二的架構示意圖如下

一次訂單系統重構實踐

經過階段一的驗證,基本可以保證整個介面鏈路層面的邏輯正確,外部的呼叫方不再感知接下來的改動變化。

階段二的核心是在內部的資料層面做驗證,保證落在新模型中的資料是正確無誤的。

這一階段沒有特別多的開發工作,主要操作是

1、在訂單領域層相關應用中,將對all-in-one庫的寫開關保留,讀開關關閉

2、在訂單領域層相關應用中,將對訂單庫的讀寫開關同時開啟

這時整個呼叫鏈路和資料鏈路已經完全實現了走新的介面服務和新的資料庫表。再通過產品功能層面驗證資料展現和產品流程是否正確,輔助老庫相關資料做對照,基本能夠驗證整個系統的重構的正確與否。

這個階段如果相關鏈路或環節出現問題,可以繼續通過開關的控制切回到原有的呼叫鏈路和資料鏈路。

階段二驗證通過後,後續還需要做一些收尾工作,包括去除雙寫程式碼、去除程式碼中的開關及歷史程式碼邏輯等等。

專案重構實施

整個架構方案確定後,接下來的重點是制定重構專案的計劃,鎖定相關資源,確定重構專案的各個里程碑節點。

專案計劃的制定,不僅僅是關注訂單模組本身的改造開發,還包括識別相關資源方和呼叫方,推動專案排期和落地。

在大部分的程式設計師認知中,只要自己系統沒有大問題,都不願意做相關的改動,畢竟任何一點改造都會額外的工作量,也會對系統的穩定性有著未知的影響。另外業務方也可能會對重構有排斥,這時就需要搞定關鍵人物,將改造的利弊陳述清楚,有時甚至需要上升到更高的層級去推動。最終能夠和相關方達成一致,確定改造的時間計劃,提前鎖定對應的開發、測試資源,保障整個重構的順利進行。

開發階段的任務既包括重構相關的介面改造開發,還需要考慮新老庫表模型切換所做的相容,包括新老庫表資料遷移相容、訊息佇列相容、快取相容等。

在開發完成後,需要做新老庫表資料遷移的模擬演練,以驗證老的表資料匯入到新庫表後,流程和展現不會出現問題。

系統級的重構改動,不可或缺的是全流程的測試驗證。

為了保證測試的充分性,當時我們採取了以下幾點關鍵措施:

1、通過已經沉澱和新增加的介面自動化用例,對大部分介面的響應和返回值做多次的跑批驗證

2、通過測試人員不斷的交叉測試,對可能遺漏的業務場景驗證

3、通過將線上的流量複製重放,對新的介面進行邏輯驗證

4、通過預發環境的流量灰度,對全流程的業務做模擬驗證

系統重構在開發測試完成後,面臨的另外一個重要問題是上線。

首先,制定詳細的上線計劃,將上線步驟事項按照先後順序全部羅列出來

其次,每個上線步驟事項需要預估出操作的時間並明確責任人

第三,對任一環節可能出現的問題,提出假設並給出解決預案,防止上線中途出現問題因慌亂導致可能出現的異常。

最後,統一指揮,有序切換流量做灰度驗證,保障整體流程正常。

上線過程中,藉助已有的監控系統,用於觀察系統、服務、介面等各項資料指標的變化情況,判斷上線中的每個環節是否有異常。

上線完成後,通過逐步的控制灰度流量佔比,驗證流程和資料是否正常,進而驗證整個重構是否成功完成。

在訂單模組重構的過程中,其它模組也在改造和推動中,經過接近半年左右的時間,基本完成了對原有all-in-one服務的完全拆分重構。

整個架構也在後續的迭代中不斷進行著新的重構和演進,包括在介面層前置設計閘道器接入層、訂單服務的細化拆分、ES對查詢場景的替換改造、業務邏輯層的中臺化演進等。

05 總結

總結在整個重構過程中的幾個關鍵步驟

  • 分析目前系統的問題點,找到最重要最優先要突破的點

  • 確定重構所要達成的目標、方向及限制條件

  • 確定重構涉及到的核心技術方案及可行性

  • 梳理重構所涉及到的需求、場景及相關上下游依賴方

  • 設計明確和完善的技術方案

  • 制定詳細的專案計劃,鎖定資源和里程碑節點並推進

  • 全流程的測試驗證

  • 詳細完備的上線計劃

  • 不可或缺的灰度驗證

系統重構是一件耗時耗力的工作,但同時也是對自身綜合能力的一個巨大挑戰和鍛鍊,期間會遇到各種各樣新的問題。但正是通過這些真實的實戰,在不斷的重構中發現自身的能力瓶頸,去學習和成長。

如果你也有相關經歷和想法,也歡迎與我交流。

相關文章