從300萬行到50萬行程式碼,遺留系統的微服務改造

技術瑣話發表於2022-12-05

在傳統企業甚至網際網路企業中往往存在大量的遺留系統,這些遺留系統大多都能夠正常工作,有的可能還執行著關鍵業務或者持有核心資料。但是,大部分遺留系統通常經常存在技術陳舊、程式碼複雜、難以修改等特點。筆者曾經維護過一個Perl實現的網站,在2015年被解耦前,它已經工作了十幾年,為公司佔領市場立下了汗馬功勞。奈何技術陳舊,維護困難,最後在微服務化過程中慢慢淡出。

可見,隨著時間的推移,遺留系統的維護和管理的成本越來越大。在向微服務架構全面轉型的過程中,這些遺留系統就像一隻只“攔路虎”,阻擋微服務轉型之路。如何在不影響業務的同時,以更安全、更高效、更低成本的方式將這些遺留系統進行微服務改造,使之順利融入微服務架構,並充分利用到微服務架構的優勢呢?本章將詳細介紹如何解決遺留系統的微服務改造問題。

一、遺留系統綜述

1. 什麼是遺留系統

“遺留系統是一種舊的方法、技術、計算機系統或者應用程式,它意味著系統過時了或者需要被取代。”

這是維基百科上對於遺留系統的定義,對於“遺留”的含義有明確闡釋,即過時或者需要被取代的系統。正因為如此,遺留系統通常具有一些類似的特徵。

  • 龐大的單體應用:遺留系統大多是多年積累下來的“巨無霸”系統,以單體應用形式呈現。

  • 難於修改:多年的程式碼累積過程中疏於重構,導致程式碼的可讀性和可擴充套件性較差。

  • 維護成本很高:在龐大的程式碼中尋找bug的根本原因可能會比較困難。此外,運維也是難題,比如在本章開頭提到的Perl系統,沒有APM工具支援它的監控。

  • 學習成本高昂:由於設計陳舊或技術過時,可能瞭解遺留系統技術和業務的人已經很難找到。

  • 缺乏質量保障:由於過去缺少投入,許多功能基本上沒有自動化測試來保障質量。

這些問題隨著日積月累,可能會給團隊帶來越來越多的負擔,直到無法承受其管理和維護成本。所以遺留系統在微服務架構下如何進行改造,是大多數企業在面向微服務轉型時都不得不面對的問題,而且急需儘早考慮、儘快解決,才能避免問題累積到一定程度後集中爆發。

2. 直接重寫遺留系統可行麼

遇到遺留系統的改造問題時,不少人可能會首先想到一個直截了當的方案:推翻重寫,用全新的系統一次性替換掉遺留系統。採用這種方式,在落地過程中總會發現種種問題,導致新系統無法順利切換,舊系統又無法完全替代。常見的問題有以下幾種:

  • 上線困難,業務阻塞風險高:在遺留系統重寫的過程中,往往會有新增需求對遺留系統的功能進行修改,在新系統未上線前,這些需求要麼被阻塞,要麼需要付出雙倍的工作量在遺留系統和新系統上同時實現,無論哪種選擇對團隊都是很難接受的。

  • 影響面不可控,系統改造週期長:遺留系統往往執行著關鍵業務或者持有核心資料,會被多個上層服務所呼叫。一旦決定開始重寫,其影響面無法評估,需要極為小心,考慮周全,客觀上會造成系統改造週期被無限期拉長。

  • 學習成本高,知識傳遞週期長:遺留系統的改造週期越長,對系統的學習成本就會隨之升高。在這個過程中隨著人員的正常流失,團隊對業務和技術的熟悉程度會逐漸降低,而新人需要花費更多的時間才能熟悉遺留系統改造過程中必要的知識和技術。

綜上所述,寄希望於直接重寫遺留系統、進行一次性替換而解決微服務改造問題是不現實的,必須探索一條能夠持續可演進的道路,實現快速、低成本、影響面可控的改造效果。

二、遺留系統改造策略

對遺留系統的改造,既要不影響業務,實現平滑、安全的過渡,又要保證能夠高效、穩步地推進,這對於軟體開發者來說是個不小的挑戰。

結合筆者的經驗,在遺留系統改造過程中可以採取以下思路:

  • 遵循“演進式改造流程”,優先改造最具價值的部分,並保證改造過程中的風險可控。

  • 採用“絞殺者模式”,透過逐步替換而非一次性替換的方式,來保證新舊系統的平滑過渡。

  • 採用“挎鬥模式”,將不容易改造的遺留系統接入微服務環境中。

1. 演進式改造流

演進式改造流程是一種以逐步演進的方式對遺留系統進行改造的流程,其過程如圖6-1所示。

從300萬行到50萬行程式碼,遺留系統的微服務改造

圖6-1  對遺留系統的演進式改造流程


1.1 構建服務路標圖

動手進行改造之前,首先需要構造出一個服務路標圖,來粗粒度地展示在理想情況下所期望的各個服務及相互之間的依賴關係。以該圖作為路標,指導我們著手進行服務化改造。當然,服務路標圖可以不必苛求完整,可以隨著開發過程的演進而不斷完善。構建路標圖的過程可由架構師、業務分析師及技術負責人共同參與,構建路標圖的方法可以參考3.1.1小節的“服務拆分策略”。服務路標圖構建好之後應在團隊中共享並接受來自各個方面人員的反饋。

1.2 服務選擇

有了服務路標圖之後,也許會發現改造過程千頭萬緒,不知如何選擇合適的部分優先進行改造。此時不妨遵循價值最大化的原則,從多種角度去制定優先拆分策略,比如:

  • 優先拆分相對獨立的部分,獨立業務與舊系統之間的耦合相對較小,比較容易實施。

  • 優先拆分頻繁變更的部分,可以透過拆分為新的服務實現獨立部署與快速上線,對於提高團隊總體交付效率價值較大。

  • 優先拆分有特殊資源佔用需求的部分,比如將計算密集型任務拆分為新服務,並恰當地利用雲基礎設施的彈性伸縮能力對新服務例項進行擴縮容,有助於提高系統總體效能並降低成本。

1.3 服務改造

選好優先需要改造的部分後,著手將這部分業務功能遷移到微服務架構下實現。改造過程中,通常需要解決這樣的問題:

  • 新舊系統可能需要不同的資料來源,或具有不同的資料庫結構,怎樣解決資料之間的同步和依賴問題?

  • 單體的舊系統需要拆分為多個服務時,怎樣實現安全的漸進式拆分?

根據改造需求的不同以及資料依賴關係,具體可以分為三種不同的改造場景,每種場景下的技術實現各有不同。

1.4 業務驗證

業務功能遷移到微服務架構下實現後,需要驗證新的服務是否滿足業務需求。在新服務上線投入使用並穩定後,可以從遺留系統中移除原有的程式碼模組,如有需要時,一併移除資料同步任務。

1.5 迭代最佳化

至此已經對一部分遺留系統的業務完成了微服務改造,對於剩餘的部分,可以按照類似的方法迭代進行,重新審視服務路標圖,選出下一個需要改造的業務,繼續進行最佳化,直到完成既定的微服務改造目標。

2. 絞殺者模式

在微服務架構還未流行起來之前,對於增量式的大規模軟體改造,常用的是名為“抽象分支”的方法,如圖6-2所示。

從300萬行到50萬行程式碼,遺留系統的微服務改造

圖6-2  抽象分支

元件解耦:在需要被替換的元件與元件消費者之間建立一個抽象層,這一層只根據需要宣告抽象的介面或協議,具體的實現仍存留於待替換元件中。這樣就透過引入抽象層實現了待替換元件與元件消費者之間的解耦。

  • 新舊元件共存:開發新元件實現同一套抽象層介面,並與待替換元件同時在系統中工作,但開始時只將少部分功能在新元件中實現,或者只有少部分生產環境流量匯入到新元件上。

  • 逐步替換:隨著功能逐漸完備和技術逐漸成熟,新元件承擔越來越多的生產環境流量。經過全面考驗後,新元件可以完全替代舊元件之時,舊元件就可以正式下線了。此時如果有需要也可以移除抽象層。

對遺留系統的微服務改造策略,也可以借鑑“抽象分支”的思路,只不過在微服務架構下,抽象層是由一個獨立的門面(Facade)服務實現,該服務將請求轉發到新系統或者舊系統,起到路由作用。

在改造過程中,新舊系統會同時存在,共同協作對外提供價值。隨著改造過程的推進,新系統提供的功能和價值越來越多,逐步地取代原有遺留系統的功能,使用者流量也會越來越多地匯入到新系統上,最後原有遺留系統不再被使用時,就可以安全地下線了,此時門面服務也可以安全地移除。

這種平滑的過渡方法通常被稱為“絞殺者模式”。遺留系統被逐步“絞殺”的過程,如圖6-3所示。

從300萬行到50萬行程式碼,遺留系統的微服務改造

圖6-3  絞殺者模式

絞殺者模式特別適合用於對複雜度較高的大型遺留系統進行逐步改造,但在遷移過程中也需要注意以下問題:

  • 考慮新系統和遺留系統之間的資料共享或者同步方式。

  • 確保絞殺者門面服務不會出現單點故障或成為效能“瓶頸”。

3. 挎鬥模式

傳統企業中存在大量的遺留系統,要對這些遺留系統全部進行微服務化改造的成本會很高,並不現實,而且有些遺留系統甚至是無法完全改造的。對於這些系統,我們的選擇並不一定是將其進行微服務化改造,而是將其接入到微服務環境中,與其他服務共同協作來實現業務需求。

  • 然而將各種形態各異的遺留系統接入到微服務環境中並不容易,存在以下常見問題:

  • 需要對原有程式碼進行一定修改,但遺留系統有時本身已經難於修改,而且如果原系統是由多種語言構成的,就要為每種語言考慮解決方案。

接入程式碼如果是和原系統執行在同一程式中,就意味著沒有很好的隔離,可能會因為接入程式碼的一點小問題造成原系統無法工作。那麼是否存在低成本的方法,將遺留系統接入到微服務環境中呢?一種方法是使用挎鬥模式,如圖6-4所示。“挎鬥”一詞來源於帶挎斗的摩托車。

從300萬行到50萬行程式碼,遺留系統的微服務改造

圖6-4  挎鬥模式

如圖6-4所示,具體到遺留系統接入場景下,挎鬥模式就是將接入功能程式碼集中在一起,作為一個獨立的程式或服務,為不同語言的遺留系統提供一個同構的接入介面。在部署結構上,挎鬥服務與原遺留系統緊密相關,原遺留系統在哪裡它就在哪裡。對於原遺留系統應用程式的每個例項,旁邊都部署和託管了一個挎鬥例項。挎鬥是支援與原應用一起部署的程式或服務。

使用挎鬥模式的好處有以下幾個:

  • 挎鬥服務是獨立執行的程式或服務,與原遺留系統的實現語言無關,不需要為每種語言各開發一種挎鬥。

  • 由於是非侵入式的接入方法,通常不需要改寫原遺留系統的程式碼,可以實現零修改成本的接入。

  • 挎鬥服務與原遺留系統相鄰部署,可以訪問與原系統相同的資源,有時可以拿來作為監控服務的接入代理。

  • 雖然增加了一些通訊成本,但是由於挎鬥與原系統相鄰部署,增加的通訊成本往往很少,延遲很低。

在使用挎鬥模式時,也需要注意以下問題:

  • 注意與原系統相鄰部署,降低通訊時延。

  • 注意程式間採用與語言無關的通訊機制,如REST。

  • 考慮使用容器化的部署方式,比如將跨鬥服務和遺留系統部署在同一個Pod中。

  • 考慮放入挎斗的功能,是作為單獨的服務或是傳統的守護程式執行方式。

ServiceMesh就是採用了挎鬥模式的思路,在每個服務近端部署一個代理,幫助遺留系統接入ServiceMesh,享受服務治理帶來的好處。

三、遺留系統改造場景

在進行具體的改造前,可能會遇到如下的挑戰:

  • 新舊系統可能需要不同的資料來源,或具有不同的資料庫結構,怎樣解決資料之間的同步和依賴問題呢?

  • 單體應用下的舊系統需要拆分為多個服務時,怎樣實現安全的漸進式拆分?

下面根據遺留系統改造過程中的常見場景,來一一解答這些問題。遺留系統的常見改造場景,如圖6-5所示。

從300萬行到50萬行程式碼,遺留系統的微服務改造

圖6-5遺留系統的常見改造場景

如圖6-5所示,對於某個具體改造需求,可以分為以下兩種不同的場景。

  • 實現新業務:需要在系統中增加新業務,與現有業務相對獨立。根據新業務與現有業務之間.

  • 否存在資料依賴關係,又可分為兩種子場景,即與現有業務資料獨立或者與現有業務資料依賴。

  • 對現有業務微服務化:需要將系統的現有業務改造為微服務架構,比如對現有業務的拆分和服務化工作。

改造場景1:實現新業務時與現有業務資料獨立

適用場景:新業務與現有業務資料獨立,不存在依賴。

實現方案:新業務可以透過單獨的服務和資料庫來實現。在消費者請求和底層系統之間引入一個絞殺者門面服務,該服務負責將請求按照業務不同路由到遺留系統和新業務服務上即可,如圖6-6所示。

從300萬行到50萬行程式碼,遺留系統的微服務改造

圖6-6改造場景1:業務資料獨立


由於和遺留系統的資料之間無耦合,新服務的技術棧、工具選型就比較靈活,充分利用到微服務技術的異構性。

改造場景2:實現新業務時與現有業務資料依賴

新業務與現有業務有資料依賴時,根據資料持有者不同,有兩種處理方案。

  • 方案A:遺留系統持有資料,新業務訪問共享資料。

  • 方案B:新業務服務持有資料,透過資料同步解決資料依賴問題。

方案A:遺留系統持有資料,新業務訪問共享資料

適用場景:允許新業務訪問遺留系統的共享資料,或者資料庫分離成本較高。

實現方案:在允許新業務服務直接訪問遺留系統的資料庫的情況下,最簡單的一種實現方案是新業務服務直接連線遺留系統資料庫獲得資料,如圖6-7所示。

這種方案的實施成本相對較低,與原有資料來源進行整合即可,但缺點也在於此,由於直接使用了資料庫作為整合方式,新業務服務仍與原遺留系統資料存在直接耦合,原資料庫的變動會直接影響到新業務服務的實現,而且對於新舊業務的資料訪問許可權與控制也需要納入考慮範圍內。針對這些問題,也可以考慮另外一種方案,即新業務服務透過遺留系統所提供的API來獲得資料,如圖6-8所示。

從300萬行到50萬行程式碼,遺留系統的微服務改造

圖6-7改造場景2:直接訪問遺留系統資料


從300萬行到50萬行程式碼,遺留系統的微服務改造

圖6-8改造場景2:透過API訪問遺留系統資料


這種方案的優點是可以透過API隔離資料變化,避免新業務服務與原遺留系統資料之間的直接耦合。可以在API中實現對資料的處理邏輯,滿足新業務服務的資料需求。API只對外提供新業務服務允許訪問的資料域,從而實現資料許可權的控制,更適用於資料庫直接訪問受限制的場景。但其引入的額外成本是需要在原遺留系統上增加API實現的工作,有時因為遺留系統的技術陳舊、結構複雜等原因,會使得這部分工作比較難於執行。

方案B:新業務服務持有資料,透過資料同步解決資料依賴問題

不難看出,方案A中的方法多多少少都對原系統有一定的侵入性,或與原資料庫直接耦合,或需要對原遺留系統進行修改。物件導向設計原則中的“開閉原則”,即“對擴充套件開放,對修改封閉”,可以幫助我們實現靈活可擴充套件的軟體架構,在這裡也同樣適用。因此可以考慮在原有系統基礎上進行擴充套件,而不是直接修改原遺留系統,於是誕生了另一個方案:新業務服務持有資料,透過資料同步解決資料依賴問題。

適用場景:不允許新業務訪問遺留系統的共享資料,或者資料庫分離成本不高。

實現方案:具體實現方法是建立獨立的資料庫供新業務服務所使用,而新資料庫與遺留系統資料庫之間透過一個專門的資料同步服務進行同步,將新資料庫中的資料轉化為與遺留系統資料可相容的形式,並遷移過去。資料同步服務又稱ETL(Extract、Transform、Load)服務,其主要職責是讀取、轉換和同步資料,如圖6-9所示。

從300萬行到50萬行程式碼,遺留系統的微服務改造

圖6-9改造場景2:透過ETL進行資料同步

該方案的優點在於使用了獨立的資料庫,對原遺留系統的侵入性較低,新業務服務持有新的資料庫,與原遺留系統解除了直接耦合,新業務服務和新資料庫的選型都可以比較靈活。所有新舊資料之間的轉換邏輯都在ETL服務中實現,以後任何資料轉換與同步的邏輯變化都只與ETL服務有關,無須再去修改原遺留系統和新業務服務。但該方案帶來的成本是需要額外實現一個ETL服務。另外由於需要在獨立的兩個資料庫之間進行同步,可能只能滿足資料的最終一致性而無法做到強一致性。另外也要考慮ETL服務的可用性,避免引入新的單點故障。

如果進一步考慮到資料隔離問題,避免直接暴露新服務的資料庫資料,還可以讓ETL服務透過新業務服務的API來訪問資料,如圖6-10所示。這種方案進一步解除了ETL服務與新業務資料庫之間的耦合,新增的API還可以進一步被複用,作為其他服務消費者訪問資料庫的介面。

從300萬行到50萬行程式碼,遺留系統的微服務改造

圖6-10改造場景2:透過ELT訪問新業務服務的API進行資料同步

改造場景3:對現有業務微服務化

大多數遺留系統在單體應用架構下已經承載了許多關鍵業務或持有核心資料,對其進行微服務化改造時,一次性的重構風險是比較大的。因此,更為提倡透過數次小的重構來逐步實現微服務化改造。

適用場景:對遺留系統承載的現有業務微服務化,實現漸進式的重構。

實現方案:以一個含有多模組的單體應用遺留系統改造為例,透過以下三個步驟拆分為業務資料分離的兩個服務。

1.將內部程式碼呼叫修改為本地REST介面呼叫:將被調函式修改為REST介面暴露出來,呼叫者模組透過對本地REST介面呼叫完成與原有業務等價的功能。此時還未拆分服務,仍然是作為一個服務整體上線。

2.將本地REST介面呼叫改為服務間REST介面呼叫:拆分服務,將原有的呼叫者模組拆分為獨立服務,REST介面呼叫地址改為目標介面所在的服務地址。這一步介面變動的成本相當小,重點在於讓拆分後的服務能夠獨立部署與上線。同時,為避免一次引入過多變更,此階段拆分後的服務仍然直接訪問原資料庫的共享資料。

3.業務資料分離:將拆分後的服務所需的資料分離出來,作為新服務的獨享資料庫所持有。兩個資料庫之間的資料依賴問題,可以借前文所述的資料同步方案(ETL服務)解決。

按照上述過程,根據需要不斷迭代,將原遺留系統的業務逐步微服務化,總體過程的示意圖,如圖6-11所示。

從300萬行到50萬行程式碼,遺留系統的微服務改造

圖6-11改造場景3:對現有業務微服務化

如何對遺留系統的資料庫進行拆分

在上述幾個改造場景中,有些步驟涉及對遺留系統的資料庫進行拆分。那麼在多服務共享資料庫的情況下,如何決定首先拆分哪個服務的資料庫?哪種拆分順序的工作量最小呢?一種方法是採用資料“讀依賴最小”的順序進行拆分,這種方法的實施可以概括為以下三個步驟:

1.表拆分,解除服務之間的資料關係耦合:列出各個服務對錶的讀寫關係,如果只有一個服務對某個表進行寫操作,不用拆分該表;當有多個服務對某個表進行寫操作時,首先考慮將這張表拆分成多張有連線關係的表,將其轉化為被某個服務單獨進行寫操作的表。

2.繪製服務依賴關係圖:在每張表被獨立服務單獨進行寫操作的情況下,按照如下規則構造有向圖,即將所有服務作為有向圖中的點,當服務A需要從服務B的資料庫讀取資料時,畫一條由B指向A的有向邊,表示服務A依賴於服務B;依此類推,直到繪製完整個依賴關係圖。

3.庫拆分,解除服務之間的資料庫耦合:以有向圖中各節點的出度(即從節點出發的邊的條數)作為該服務被依賴數,進行排序,挑選被依賴數最小的服務,首先對其進行資料庫解耦,把該服務下的資料庫表獨立出來,並在該服務裡提供資料介面,以供依賴於它的服務呼叫。

重複第3步,直到所有資料庫被拆分為由各個服務獨享的資料庫。

例如,如圖6-12所示,是一組包含四個服務的依賴關係圖,服務右上角的角標表示該服務的被依賴數。得知簡訊服務的被依賴數為0,為當前被依賴最少的服務,可以首先將該服務的資料庫拆分出來。其次再按順序依次拆分積分服務、訂單服務和賬戶服務的資料庫,直至所有資料庫被拆分為由服務所獨享的資料庫。

從300萬行到50萬行程式碼,遺留系統的微服務改造

圖6-12服務依賴關係圖示例

四、遺留系統改造案例

本節以筆者曾親身參與改造的遺留系統為案例,介紹如何將一個大型遺留系統向微服務架構進行遷移。

1. 改造前的系統情況

該系統是一個用於提供全球房產資訊搜尋服務的大型門戶平臺,核心功能主要包括如下幾點,如圖6-13所示。

  • 為使用者提供基於地理位置、關鍵字的房產資訊搜尋功能。

  • 提供基於定製搜尋條件的房源資訊訂閱。

  • 提供桌面端、平板端、手機端等多種不同方式的訪問。


從300萬行到50萬行程式碼,遺留系統的微服務改造

圖6-13系統現狀

該系統是從多年前收購的一個通用搜尋平臺改造而來的,整體為一個規模龐大的單體應用,使用同一個程式碼庫,技術棧主要以Java為主,資料庫為Postgre,搜尋引擎使用FASTSearch(歷史原因),程式碼量大約在300萬行左右。

隨著業務演進的速度越來越快,單體應用的弊端導致系統變更成本越來越高。即使修改幾行程式碼也需要經過冗長的測試以及釋出流程,系統釋出才能生效。面對這些問題,團隊決心使用微服務架構,將未來的新功能全部以微服務實現,並將系統原有功能逐步遷移到微服務架構下。

2. 改造過程

步驟1:透過遺留系統API提供資料

當時,研發團隊接到的第一個特性需求如下所示:

  • 支援NativeApp提供核心功能

  • 三個月上線首個版本

此時,面臨的挑戰主要包括以下兩個:

  • 該系統的主要研發成員在海外,國內團隊剛成立,新人多,知識遷移成本高。

  • 系統本身是基於商業通用搜尋平臺上進行的二次開發,邏輯複雜,程式碼耦合度高,變更成本高。

採取的方案主要包括如下幾部分:

  • 在遺留系統上封裝現有邏輯,提供基礎API。

  • 重新實現一個服務,類似之前章節提到的BFF模式,為NativeApp提供資料。

  • 該服務透過訪問遺留系統提供的基礎API獲得所需要的資料。

  • 該服務內實現必要的轉換,提供給NativeApp介面進行呈現。

利用微服務架構的異構性,新服務命名為AppService,使用RubyOnRails實現,並實現相關的資料轉換邏輯,為NativeApp提供資料API。透過這種方式實現的新服務,不僅能滿足NativeApp的需求,而且可以快速開發,獨立部署。

改造後的系統架構圖,如圖6-14所示。其中AppService服務提供兩組API,分別是提供房源資訊的EstateAPI和提供地理位置資訊的LocAPI。

從300萬行到50萬行程式碼,遺留系統的微服務改造

圖6-14

步驟1:透過遺留系統API提供資料

透過如上所述的方式,極大地減少了對原門戶平臺的修改,同時透過在新服務上實現相關邏輯,不僅提升了交付效率,而且該服務能夠獨立部署上線。

步驟2:透過遺留系統資料來源提供資料

接下來,研發團隊接到的特性需求如下:

  • 支援房產釋出者的資訊展示。

  • 支援地理興趣點(POI)資訊顯示。

但是這兩部分的資料在原門戶平臺的基礎搜尋API中並未包含,在面臨人手有限和交付時間緊的挑戰下,如果要在門戶平臺中新增介面,變更成本會比較高,可能無法按時交付。

面對這些挑戰,團隊決定儘量避免對原有遺留系統的直接修改,改為在AppService微服務中實現新業務邏輯,做了如下修改:

  • 直接訪問遺留系統資料來源:由於原門戶平臺的基礎搜尋API提供的資料不足,便讓AppServic

  • 透過直接訪問遺留系統資料來源的方式,獲得所需的房源、釋出者、地理興趣點等資料資訊。

  • 構建基礎搜尋模組:在AppService中構建基礎搜尋模板(EngineAPI),實現與原門戶平臺的基礎搜尋API同樣的功能,提供資料給上層API。

  • 構建業務層API:在AppService中構建業務層API,新增兩組API,分別是提供地理興趣點資料的POIAPI和房產釋出者資訊的PubAPI。

  • 獨立地理位置服務:將之前相對獨立的LocAPI,拆分出來作為獨立的地理位置服務LocService。

改造後的系統架構圖,如圖6-15所示。透過這步改造,將基礎搜尋資料的查詢功能重構到了AppService微服務內,減少了對原門戶平臺相關功能的修改。

步驟3:拆分基礎搜尋服務,替換資料來源

產品的需求持續演進,接下來,團隊需要為桌面端使用者新增支援多邊形的地理位置搜尋功能。但在現有架構下,FASTSearch搜尋引擎並不支援這個功能,這就意味著需要替換原有的搜尋引擎,需要對原門戶平臺的程式碼做大量的修改。

如何有效地實現這個特性呢?具體操作方法如下:

1.將基礎搜尋API拆分出來作為獨立服務EngineService,這樣以後關於搜尋相關的邏輯,都可以放在EngineService中實現了。

從300萬行到50萬行程式碼,遺留系統的微服務改造

圖6-15步驟2:透過遺留系統資料來源提供資料

2. 在EngineService中實現基於多邊形的搜尋。

3. 對原門戶平臺的搜尋機制重構,讓其使用EngineService獲取相關資料(之前是使用FASTSDK)。基於這種方式,門戶平臺和AppService與底層搜尋引擎隔離開來,可以方便地替換底層資料來源。

該方案的實現,如圖6-16所示。

從300萬行到50萬行程式碼,遺留系統的微服務改造

圖6-16步驟3:拆分基礎搜尋服務,替換資料來源

步驟4:構建移動端專屬的後端服務

團隊在預期時間內很快地完成了前面的各項需求,隨著業務發展迅猛,接下來需要在手機端和平板端增強使用者體驗。然而,目前手機端、平板端的實現邏輯較落伍,均是基於JSP的模板方式實現。如果要直接修改,成本高。

考慮到在螢幕尺寸、效能和顯示限制等方面,移動裝置與桌面瀏覽器的能力存在顯著差異。因此,移動應用對後端的需求與桌面UI是不一致的。

如果仍然使用原有的門戶平臺邏輯,則需要同時為這兩種客戶端提供資料,這極大地增加了維護的成本。於是,團隊借鑑了BFF模式,將手機端和平板端網站的特定後臺需求,拆分為一個獨立的後臺服務SPA-Web,作為後端實現。為前端提供資料。然後基於最新的前端技術,採用輕量級、更有效的單頁面方式實現前端,如圖6-17所示。

從300萬行到50萬行程式碼,遺留系統的微服務改造

圖6-17

驟4:構建移動端專屬的後端服務

透過為移動端建立一個專屬的後端服務,可以為移動端的使用者進行專門的最佳化,量身打造最佳體驗。同時,由於微服務架構下的這個新服務足夠的小而簡單,就更容易進行修改、部署和上線了,也更容易在效能和行為方面進行精心的最佳化。

步驟5:基於資料繼續劃分微服務

隨著團隊的微服務化實踐越來越成熟,整個系統的架構趨向於朝更細的粒度演進。

系統典型的業務場景分為待售房源(Buy)、已售房源(Sold)、待租房源(Rent)等。所以,團隊基於這些業務繼續劃分單獨的搜尋API以及獨立的資料儲存機制,每種資料由獨立的ElasticSearch 叢集儲存,並利用前後端分離的機制實現前後端的解耦。

解耦後的系統,如圖6-18所示。

從300萬行到50萬行程式碼,遺留系統的微服務改造

圖6-18步驟5:按照資料劃分微服務

最終實現的服務,如下表所示。

服務名稱

主要描述

UserService

處理使用者資訊的認證和鑑權

SavedSearchService

處理使用者儲存的訂閱條件

POIService

處理POIService相關的資訊

LocService

提供地理位置相關介面

BuyService

提供待售房源相關的介面

SoldService

提供已售房源相關的介面

RentService

處理待租房源相關的邏輯

對於系統中使用的服務支撐元件,如服務註冊發現機制、集中化配置資訊等機制,其實現和在之前的章節中闡述的差不多,這裡就不展開贅述了。

3. 改造結果

可以看到,經過上面一系列步驟後,原有的門戶平臺已逐漸遷移為微服務的系統,原有的大約300萬行的程式碼也只剩下了大約50萬行,繼續提供著業務價值。團隊對遺留系統的修改和變更成本已經大大減少,總體交付週期也大大縮短,技術的升級帶來效能、可維護性的提升,充分享受到了微服務架構小而美的好處。

小結

本章介紹了遺留系統的特點、改造策略和場景,並結合一個實戰案例進行了講解。目的是幫助讀者從以下方面掌握對遺留系統的微服務改造方法:

  • 遺留系統是“需要被替代的系統”,往往存在類似的特徵,如難於修改、學習和維護成本高、缺乏質量保障等。

  • 透過直接重寫並一次性替換遺留系統解決微服務改造問題是不現實的,可能會導致上線困難、影響面不可控、學習成本高等問題的產生。

  • 對於遺留系統的改造過程,應當採取逐步替換而非一次性替換的策略。通常採用“演進式改造流程”和“絞殺者模式”來保證整個改造過程可控,並實現平滑過渡。

另外,對於遺留系統的改造需求,本章將其細分為三種場景,如新業務資料獨立、新業務資料依賴以及現有業務服務化。透過對這些場景的分析,能有效地指導讀者進行微服務的演進。

本文節選自《微服務架構與實踐(第2版)》一書,王磊等著,電子工業出版社出版。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31562044/viewspace-2650826/,如需轉載,請註明出處,否則將追究法律責任。

相關文章