如何從複雜單體應用快速遷移到微服務?

架構師springboot發表於2019-01-09
如何從複雜單體應用快速遷移到微服務?

為什麼要向微服務轉變

整體式(monolithic)應用程式很龐大(程式碼行數方面)、很複雜(功能依賴和資料等方面),為跨地區的成千上萬使用者提供服務,需要多個開發人員和 IT 工程師。

整體式應用程式可能類似下圖:

圖 1:整體式應用程式的基本結構

有時,即使具有所有這些特點,應用程式最初也可能順暢執行,可能在應用程式可擴充套件性或效能方面不會遇到挑戰。但用著用著會出現問題,問題因應用程式而異。

比如對於雲或 Web 應用程式而言,由於更多使用者使用服務,你可能遇到可擴充套件性問題,或者由於更長的構建時間和迴歸測試,定期釋出新的更新可能變得成本高、難度大。

如圖 2 所示,整體式應用程式的使用者或開發人員可能遇到右邊列出的一個或多個問題。

圖 2:整體式應用程式的潛在問題

這時遷移到微服務可能聽起來不僅僅是時髦的想法,更像是大救星。應用程式的遷移會類似圖 3 所示:

圖 3:從整體式應用程式向微服務轉變

那麼,如何進行這種轉變?有兩種可能的場景:

  • 建立全新的應用程式。
  • 轉換或遷移已經存在的整體式應用程式。

後一種場景的可能性大得多,但無論目前的情況如何,都有必要了解這兩種場景的來龍去脈。

使用微服務建立新應用程式

我還沒有看到很多從頭開始構建基於微服務的應用程式的真實場景。通常,應用程式已部署到位,我搞過的大多數應用程式更多是從整體式架構向微服務架構轉變。

在這種情況下,架構師和開發人員的意圖一直是重用一些現有的實現。但由於技能在市場上非常普遍、一些成功的實現釋出,我們會看到從頭開始構建基於微服務的應用程式的更多例子,因此當然有必要探究這種場景。

假設你已摸清了所有需求,準備好處理要構建的應用程式的架構設計。你在入手時需要考慮許多常見的最佳實踐,這些實踐在下面各節中有介紹。

組織準備

你要問自己的第一個問題是,貴組織是否準備好向微服務轉變。

這意味著貴組織的各個部門現在需要從以下方面對軟體的構建和釋出進行不同的思考:

  • 團隊結構。整體式應用程式團隊(如果有的話)需要分解成幾個知道微服務最佳實踐或受到培訓的小規模高績效團隊。

如圖 3 所示,新系統將包含一組獨立服務,每個服務負責提供特定服務。這是微服務模式的一大優勢:減少了通訊開銷,包括多場不間斷會議。

團隊應按照所要解決的業務問題或領域加以組織。然後,溝通變為敲定要遵循的一套標準/協議,那樣這些微服務就能彼此協作。

  • 每個團隊必須準備獨立於其他團隊運作。它們的規模應相當於標準的 Scrum 團隊,否則溝通會再次成為問題。執行是關鍵,每個團隊都應能夠滿足不斷變化的業務要求。
  • 工具和培訓。一個關鍵要求是組織準備投入於新工具和人員培訓的情況。在大多數情況下,現有的工具和流程需要停用,採用一套新的。

這需要投入大量資本,致力於招聘擁有新技能的人員,並重新培訓現有工作人員。從長遠來看,如果採用微服務的決定是正確的,組織會看到成本節省,因而收回投入。

基於服務的方法

與整體式應用程式不同,若是微服務,你需要採用自我維持的基於服務的方法。

應用程式好比是一組鬆散耦合的服務,這些服務相互通訊以提供完整的應用程式功能。

應將每項服務視為有其生命週期的獨立服務,可由獨立團隊開發和維護。這些團隊要從各種技術中進行選擇,包括最適合其服務要求的語言或資料庫。

比如針對電子商務站點,團隊要編寫一個完全獨立的使用記憶體資料庫的服務,比如購物車微服務,以及使用關聯式資料庫的另一項服務,比如訂購微服務。

實際應用程式可能將微服務用於基本功能,比如身份驗證、帳戶、使用者註冊和通知,業務邏輯封裝在 API 閘道器中,API 閘道器基於客戶端和外部請求呼叫這些微服務。

提醒一下:微服務可能是一個開發人員實現的小服務,也可能是需要多個開發人員的複雜服務。就微服務而言,大小無關緊要;它完全依賴服務要提供的一項功能。

此時必須考慮的其他方面是擴充套件、效能和安全。擴充套件要求可能不一樣,應在每個微服務層面根據需要來提供。應在所有層面考慮安全,包括靜態資料、程式間通訊和傳輸中資料等。

程式間(服務到服務)通訊

必須考慮的關鍵方面是安全和通訊協議。非同步通訊是最佳選擇,因為它可確保所有請求正常執行,不會長時間佔用資源。

使用 RabbitMQ 等訊息匯流排可能有利於這種通訊。它很簡單,可以擴充套件到每秒數十萬條訊息。

為防止訊息傳遞系統在發生故障後成為單一故障點,必須正確設計訊息傳遞匯流排以實現高可用性。其他選項包括另一種輕量級訊息傳遞平臺 ActiveMQ。

安全是該階段的關鍵。除了選擇正確的通訊協議外,可使用 AppDynamics 之類的行業標準工具來監控和衡量程式間通訊。須自動向安全團隊報告任何異常情況。

若有數千個微服務,處理一切確實變得複雜起來。後面會解釋如何藉助發現服務和 API 閘道器解決此類問題。

技術選擇

向微服務轉變的最大優勢是讓你可以選擇。每個團隊可以獨立選擇最適合特定微服務的語言、技術和資料庫等。

若採用整體式方法,團隊通常沒有這樣的靈活性,因此確保你沒有忽視並錯過該機會。

即使團隊在處理多個微服務,也要將每個微服務視為獨立的服務並進行分析。

為每個微服務選擇技術時,必須牢記可擴充套件性、部署、構建時間、整合和外掛可操作性等。

如果是資料較少但訪問較快的微服務,記憶體資料庫可能最合適,而其他微服務可能使用同樣的關聯式資料庫或 NoSQL 資料庫。

實現

實現是關鍵階段,這時候所有培訓和最佳實踐知識派得上用場。

要記住的幾個關鍵方面包括:

  • 獨立性。每個微服務都應高度自主,有自己的生命週期並以此進行處理。它的開發和維護不需要依賴其他微服務。
  • 原始碼控制。必須部署適當的版本控制系統,每個微服務要遵循標準。統一程式碼庫也很有幫助,因為它可以確保所有團隊使用相同的原始碼控制。

它有助於程式碼審查等各個方面,便於在一個地方訪問所有程式碼。長遠來看,有必要對所有服務實行同樣的原始碼控制。

  • 環境。所有不同的環境(如開發、測試、模擬和生產等階段)必須得到適當的保護和自動化。這裡的自動化包括構建過程。

那樣,可以根據需要整合程式碼,大多每天進行。有幾種工具可用,不過 Jenkins 廣泛使用。Jenkins 是一種開源工具,有助於軟體構建和釋出過程實現自動化,包括持續整合和持續交付(CI/CD)。

  • 故障安全。軟體故障不可避免。須在微服務開發中解決好下游服務的故障處理問題。其他服務的故障必須是隱形的,好讓使用者看不到故障。

這包括管理服務響應時間(超時)、處理下游服務的 API 更改以及限制自動重試次數。

使用微服務時,別害怕通過使用複製貼上來重用程式碼,但這麼做要有限制。

這可能導致程式碼重複,但這勝過使用最終耦合服務的共享程式碼。微服務中,你需要的是去耦,不是緊耦合。

比如說,你將編寫程式碼以使用服務的輸出響應。每次從任何客戶端呼叫相同的服務時,你可以複製此程式碼。

重用程式碼的另一種方法是建立公共庫。多個客戶端可以使用相同的庫,但隨後每個客戶端應負責維護其庫。

如果你建立太多的庫,每個客戶端維護不同的版本,有時變得困難重重。這種情況下,你要包含相同庫的多個版本,由於向後相容性和類似問題,構建過程可能變得困難。

只要你可以控制客戶端的庫和版本數量,並對它們實行嚴格的流程,可以採用任何一種方式,這就看你的需要了。這肯定有助於避免大量的程式碼重複。

鑑於微服務數量龐大,除錯問題可能會變得困難,因此你需要在此階段進行某種檢測。

最佳實踐之一是使用唯一的請求 ID 標記每個請求,並記錄每個請求。這個唯一的 ID 標識始發請求,應由每個服務傳遞給任何下游請求。

看到問題後,你可以通過日誌清楚地回溯並找出有問題的服務。如果你建立集中式日誌記錄系統,該解決方案最有效。

所有服務都應以標準化格式將所有訊息記錄到此共享系統,以便團隊可以根據需要從一個地方(從基礎設施到應用程式)重放事件。用於集中式日誌的共享庫值得研究。

市面上有幾種很理想的日誌管理和聚合工具,比如 ELK(Elasticsearch、Logstas和Kibana)以及 Splunk。

部署

自動化是部署過程中的關鍵。沒有它,微服務模式幾乎不可能成功。可能有成百上千的微服務,對於敏捷交付而言,自動化必不可少。

設想部署數千個微服務並維護。其中一個微服務發生故障後會發生什麼?怎麼知道哪臺機器有足夠的資源來執行你的微服務?

若沒有落實自動化,應對這種情況變得非常複雜。Kubernetes 和 Docker Swarm 等各種工具可用於自動化部署過程。

操作

整個過程的操作部分也需要自動化。這裡談論的同樣是成百上千的微服務,組織能力需要足夠成熟才能處理這種複雜程度。

你需要一個支援系統,包括以下方面:

  • 從基礎設施、應用程式 API 到最後一英里效能,全部都要加以監控,並實施具有適當閾值的自動警報。考慮構建問題出現後彈出資料和警報的實時儀表板。
  • 按需可擴充套件性。若使用微服務,擴充套件是最簡單的任務。配置你想要擴充套件的微服務的另一個例項,將它放在現有的負載均衡器後面就行。

但在規模化環境中,這也需要實現自動化。只需設定一個整數值,告訴想要為特定微服務執行的例項數量。

  • API 公開。在大多數情況下應該對外公開 API 以供外部使用者使用。最好使用邊緣伺服器來完成這項任務,該伺服器可以處理所有外部請求。

它可以使用 API 閘道器和發現服務來完成任務,你可以針對每種裝置型別(比如移動裝置或瀏覽器)或用例使用一臺邊緣伺服器。Netflix 開發的一款開源應用軟體 Zuul 可用於此功能及其他功能。

  • 斷路器。向故障服務傳送請求毫無意義。因此可以構建斷路器(circuit breaker),跟蹤針對每個服務的每個請求的成功或故障。若出現多個故障,應阻止對該特定服務的所有請求一段指定的時間(即斷開電路)。

指定時間過後,應進行另一次嘗試,依此類推。一旦響應成功,重新連線電路。這應該在服務例項層面進行。Netflix 的 Hystrix 提供了開源斷路器實現。

將整體式應用程式遷移到微服務

雖然構建基於微服務的新應用程式的大多數最佳實踐也適用於遷離現有的整體式應用程式,但如果遵循另外一些準則,可使遷移更簡單、更高效。

雖然將整個整體式應用程式轉換成完全基於微服務的應用程式聽起來可能很正確,但將每項功能轉換成微服務可能並不高效,在一些情況下可能成本很高。

畢竟,你到頭來要從頭開始編寫應用程式。正確的遷移方式可能需要逐步進行,如圖 4 所示:

圖 4:基本的遷移步驟,從整體式應用程式到微服務

下一個問題是:目前的整體式應用程式從何處入手?如果應用程式確實很舊,進行分解很耗時、難度大,從頭開始可能更好。

在可以快速禁用部分程式碼、技術架構並不完全過時的其他情況下,最好先將元件重新構建為微服務,並換掉舊程式碼。

微服務標準

那麼問題變成了哪些元件應該先遷移或甚至要不要遷移。這讓我想到了所謂的“微服務標準”,這概述了選擇應遷移到微服務的功能並確定優先順序的可行方法之一。

它們是你制定的一套規則,根據組織當時的要求,決定將現有整體式應用程式的元件轉換成微服務是否適合。

這時機在這裡很重要,因為組織的要求可能不斷變化,你可能要回過頭來,將更多元件轉換成微服務。

換句話說,由於要求變化,整體式應用程式的額外元件可能適合轉換。以下是轉換過程中被視為微服務標準的幾個最佳實踐:

①你需要確定哪些功能頻繁使用

先將頻繁使用的服務或應用程式功能轉換成微服務。記住:微服務只執行一個明確定義的服務。牢記這個原則,相應地劃分應用程式。

②可能存在效能不佳的元件,其他替代方案隨時可用

可能有開源外掛,或者你可能想從頭開始構建服務。應牢記的要點之一是微服務的邊界。

只要你設計的微服務只做一件事,就很好。確定邊界常常很難,你會發現實踐一下會更容易。

檢視微服務邊界的另一種方法是,應該幾周內就能重寫整個微服務,而不是花幾個月來重寫服務。

③更好的技術替代方案或多語言程式設計

針對特定領域的語言可用於幫助解決問題域(problem domain)。這尤其適用於過去你收到許多改進請求,預計將來會繼續如此的元件。

如果你不僅認為可以使用市面上的新語言或功能簡化這種元件的實現,將來的維護和更新還會變得更容易,現在正是應對這種變化的時候。

在其他情況下,你可能發現另一種語言提供的併發抽象比目前使用的語言更容易。

可以將新語言用於特定的微服務,而應用程式的其餘部分仍然使用不同的語言。

同樣,你可能希望一些微服務非常快,可能決定用 C 語言編寫以獲得最大效益,而不是用另一種高階語言編寫。說到底是要利用這種靈活性。

④儲存替代方案或多語言永續性

大資料大行其道,如果使用 NoSQL 資料庫而不是關聯式資料庫,應用程式的一些元件可能會提供價值。

如果應用程式中的任何此類元件可得益於該替代方案,可能正是改用 NoSQL 的時候。

這些是你應該為整體式應用程式中的每個服務或功能而考慮的關鍵方面,你需要先注重這幾項的轉換。一旦你從高優先順序的部分獲得了價值,隨後可以運用其他規則。

⑤修改請求

任何軟體生命週期中要跟蹤的一個重要方面是新的改進請求或更改。由於構建和部署時間,更改請求數量更多的功能可能適用於微服務。

分離這類服務可以縮短構建和部署時間,因為你不必構建整個應用程式,只需更改微服務,這還可以為應用程式的其餘部分提高可用性時間。

⑥應用程式的某些部分總是增加部署的複雜性

在整體式應用程式中,即使某項功能未加變動,你仍得完成整個構建和部署過程。

如果存在這種情況,劃分這些元件並用微服務取代大有助益,這樣可以為整體式應用程式的其餘部分縮短總的部署時間。

⑦輔助服務

在大多數應用程式中,核心或主要的服務依賴一些輔助服務。沒有這類輔助功能,可能會影響核心服務的可用性。

比如在求助臺應用程式中,工單依賴產品目錄服務。如果產品目錄服務不可用,使用者無法提交工單。

如果存在這種情況,應將輔助服務轉換成微服務,並確保高可用性,以便它們可以更好地服務於核心服務。(這些又叫斷路器服務。)

視應用程式而定,這些標準可能需要將大多數服務轉換成微服務,目的是簡化轉換過程,那樣你可以確定優先順序,併為遷移到基於微服務的架構制定路線圖。

為服務重新設計架構

一旦確定了要遷移的轉換成微服務的功能,可以遵循前面所述的最佳實踐,開始為已選擇的服務重新設計架構。

以下是需要牢記的幾個方面:

  • 微服務定義。為每個功能定義適當的微服務,應包括通訊機制(API)和技術定義等。

考慮現有功能使用的資料,或針對微服務相應地建立和規劃資料策略。如果該功能在 Oracle 之類的密集資料庫上,遷移到 MySQL 是否有意義?

確定你將如何管理資料關係。最後,將每個微服務作為單獨的應用程式來執行。

  • 重構程式碼。如果你未改變程式語言,可以重用一些程式碼。考慮儲存/資料庫層:共享 vs 專用,記憶體中 vs 外部。

目的在於除非需要,否則不新增新功能,而是重新打包現有程式碼並公開所需的 API。

  • 開始編碼之前,確定原始碼控制和版本控制機制,並確保遵循這些標準。每個微服務都是單獨的一項,作為單獨的應用程式來部署。
  • 資料遷移。如果你決定建立新資料庫,還要遷移舊資料。這通常通過編寫簡單的 SQL 指令碼來完成,具體取決於你的原始碼和目的地。
  • 整體式程式碼。最初將現有程式碼留在整體式應用程式中,以防萬一要回滾。你可以更新其餘程式碼以使用新的微服務,或者劃分應用程式流量(如果可能),同時使用整體式版本和微服務版本。

這讓你有機會測試和關注效能。一旦有信心,你可以將所有流量遷移到微服務,禁用或刪除舊程式碼。

  • 獨立地構建、部署和管理。要獨立構建和部署每個微服務。推出新版本的微服務時,可以再次劃分舊版本和新版本之間的流量。

這意味著你可能在生產環境中執行相同微服務的兩個或更多版本。一些使用者流量可以路由到新的微服務版本,確保服務正常執行、效能良好。

如果新版本未在最佳狀態下執行,很容易將所有流量回滾到先前版本,並將新版本退回給開發團隊。這裡的關鍵是建立可重複的自動化部署流程,竭力實現持續交付。

  • 刪除舊程式碼。只有在確認一切已正確遷移並按預期執行後,才能刪除臨時程式碼,並從舊儲存位置刪除資料。務必在此過程中做好備份。

微服務的混合方法

開發人員編寫全新的應用程式時,可以直接遵循微服務架構原則和藍圖來構建應用軟體。開發人員有時遵循微服務和整體式應用程式的混合方法。

在這種情況下,他們可以將應用程式的部分元件開發成微服務,其餘部分基於某些標準遵循標準的 SOA/MVC 實踐。

其想法是,並非應用程式的所有元件都可以轉換成微服務。微服務提供了很大的靈活性,但這種靈活性要付出一些代價。

混合方法旨在兼顧靈活性和成本這兩方面,以後可以根據需要從整體式應用程式獲取元件、轉換成微服務。關鍵是在這個轉變期間牢記這兩種方法以及微服務標準。

感興趣的可以自己來我的Java架構群,可以獲取免費的學習資料,群號:855801563 對Java技術,架構技術感興趣的同學,歡迎加群,一起學習,相互討論。



相關文章