企業級容器云架構開發指南》—1.2.2 存儲虛擬化">《企業級容器云架構開發指南》—1.2.2 存儲虛擬化
620
2025-03-31
2.4 如何從單體架構遷移到微服務
雖然微服務是近年很熱門的架構選擇,但什么時候該選擇微服務架構是有一定前提的。
圖2-27是馬丁·福勒所繪制的,其中黑線指的是單體,灰線是微服務架構。實際上在最初各方面效能、效率、開發、迭代的成果都比較好,不過隨著單體越來越臃腫,各方面效能降低,這時候微服務的優勢才得以體現。任何時候都是單體優先,只有單體結構變得越來越龐大,效能降低,并滿足以下4個條件的時候才考慮進行微服務化:
圖2-27 單體架構和微服務架構在不同系統復雜度下不同的生產力
要有快速迭代的能力。
要有基本的監控。
要有快速的集成。
要有一個DevOps文化。
圖2-27表明了復雜度和生產率拐點的存在,但并沒有量化復雜度的拐點到底是多少,或者換種說法,系統或代碼庫的規模具體達到多大才適合開始進行微服務化的拆分。在一篇有趣的文章《程序員職業生涯中的 Norris 常數》中提到,大部分普通程序員成長生涯的瓶頸在兩萬行代碼左右。每一個瓶頸點的突破意味著需要新的技能和技巧,微服務合適的拆分拐點可能就在兩萬行代碼規模附近,而每個微服務的規模大小最好能控制在一個普通程序員的舒適維護區范圍內。一個受過職業訓練的普通程序員就像一個拿到駕照的司機,一般司機都能輕松駕馭100公里左右的時速,但很少有能輕松駕馭200公里或以上時速的司機,即使能夠駕駛,風險也是很高的,而能開“噴氣式飛機的飛行員級別”的程序員恐怕在大部分的團隊里一個也沒有。
另外一個實施前提是基礎設施的自動化,把1個應用進程部署到1臺主機,部署復雜度是 1×1 = 1,若應用規模需要部署200臺主機,那么部署復雜度是1×200 = 200。把1個應用進程拆分成50個微服務進程,則部署復雜度變成了50×200 = 10 000,缺乏自動化設施,僅部署就是一件“瘋狂”的事情。所以前面微服務的特征才有基礎設施自動化,這與規模有關,也是因為其運維復雜度呈乘數級飆升,從開發之后的構建、測試、部署都需要一個高度自動化的環境來支撐,這樣才能有效降低邊際成本。
微服務化的原則是:要漸進改革,不要推倒重來。不要大規模重寫代碼,重寫代碼聽起來很不錯,但實際上充滿了風險,最終可能會失敗,就如馬丁·福勒所說:“the only thing a Big Bang rewrite guarantees is a Big Bang!”
相反,應該采取逐步遷移單體式應用的策略,通過逐步生成微服務新應用,與舊的單體式應用集成,隨著時間的推移,單體式應用在整個架構中的比例逐漸下降,直到消失或者成為微服務架構的一部分。
最好從一個新的需求開始微服務化,而這個需求又比較適合微服務化。例如,稽核模塊和其他模塊沒有關聯,通信比較簡單,業務邏輯比較獨立,在這個時候我們可把它做成一個微服務。除了新服務和傳統應用,還要新增兩個模塊。一個是請求路由器,負責處理入口(http)請求,有點像之前提到的API網關。路由器將新功能請求發送給新開發的服務,而將傳統請求仍發給單體式應用。另外一個是膠水代碼(glue code),它將微服務和單體應用集成起來,微服務很少能獨立存在,經常會訪問單體應用的數據。膠水代碼可能在單體應用或者微服務或者兩者兼而有之,負責數據整合。微服務通過膠水代碼從單體應用中讀寫數據。
微服務可以通過3種方式訪問單體應用數據:
訪問單體應用提供的遠程API。
直接訪問單體應用數據庫。
自己維護一份從單體應用中同步的數據。
膠水代碼也被稱為容災層(anti-corruption layer),這是因為膠水代碼保護微服務全新域模型免受傳統單體應用域模型的污染。膠水代碼在這兩種模型間提供翻譯功能。開發容災層可能不是很重要,但卻是避免單體式泥潭的必要部分。
將新功能以輕量級微服務方式實現有很多優點,如可以阻止單體應用變得更加無法管理。微服務本身可以開發、部署和獨立擴展。采用微服務架構會給開發者帶來不同的切身感受。然而,這種方法并不解決任何單體式本身問題,為了解決單體式本身問題必須深入單體應用做出改變。下面我們來看看這么做的策略。
(1)排序模塊轉成微服務
一個巨大的復雜單體應用由上百個模塊構成,每個都是被抽取對象。決定第一個被抽取模塊一般極具挑戰,最好是從最容易抽取的模塊開始,這會讓開發者積累足夠的經驗,這些經驗可以為后續模塊化工作帶來巨大的好處。轉換模塊成為微服務一般很耗費時間,可以根據獲益程度來排序,一般從經常變化的模塊開始會獲益最大。一旦轉換一個模塊為微服務,就可以將其開發部署成獨立模塊,從而加速開發進程。
將資源消耗大戶先抽取出來也是排序標準之一。例如,將內存數據庫抽取出來成為一個微服務會非常有用,可以將其部署在大內存主機上。同樣地,將對計算資源很敏感的算法應用抽取出來也是非常有益的,這種服務可以被部署在有很多CPU的主機上。通過將資源消耗模塊轉換成微服務,可以使得應用易于擴展。
查找現有粗粒度邊界來決定哪個模塊應該被抽取,也是很有益的,這使得移植工作更容易和簡單。例如,只與其他應用異步消息同步的模塊就是一個明顯的邊界,可以很簡單、很容易地將其轉換為微服務。
(2)從哪里開始拆分:接縫
從接縫處可以抽取相對獨立的一部分代碼,對這部分代碼的修改不會影響系統的其他部分,這些接縫就可以作為服務的邊界。那么如何識別出接縫呢?我們可以使用前面所提到的限界上下文,可以通過程序中的命名空間,也可以通過工具來幫助我們,如利用Structure 101這樣的工具來可視化包之間的依賴。
(3)如何抽取模塊
抽取模塊的第一步就是定義好模塊和單體應用之間的粗粒度接口,由于單體應用需要微服務的數據,反之亦然,因此這更像是一個雙向API。因為必須在負責依賴關系和細粒度接口模式之間做好平衡,因此開發這種API具有挑戰性,尤其對使用域模型模式的業務邏輯層來說更具有挑戰。因此經常需要改變代碼來解決依賴性問題,一旦完成粗粒度接口,也就將此模塊轉換成獨立微服務了。為了實現,必須寫代碼使得單體應用和微服務之間通過使用進程間通信(IPC)機制的API來交換信息。第二步遷移就是將模塊轉換成獨立服務。內部和外部接口都使用基于IPC機制的代碼,抽取完模塊,也就可以開發、部署和擴展另外一個服務了,此服務獨立于單體應用和其他服務。
可以從頭寫代碼實現服務,在這種情況下,將服務和單體應用進行整合的API代碼成為容災層,在兩種域模型之間進行翻譯工作。每抽取一個服務,就朝著微服務方向前進一步。隨著時間的推移,單體應用將會越來越簡單,用戶就可以增加更多獨立的微服務了。
(4)雜亂依賴的根源:數據庫
為什么這么說呢?因為在通常情況下,我們在業務層的代碼已經通過分層組織到相應的包中了,但是只有數據庫是共用的,數據庫對所有的代碼都允許訪問,是一個巨大的API。
對于同一張表被多個限界上下文使用的場景,我們應該如何處理?以下是一些處理的步驟和原則:
1)分清代碼中對數據庫進行讀寫的部分。我們需要厘清代碼是如何訪問數據庫的,在什么地方讀,在什么地方寫,它們分別位于什么樣的上下文中。
2)打破外鍵關系。對于表與表之間的外鍵關系,如果這兩張表需要被拆分至兩個微服務中,我們可能需要放棄外鍵關系,同時把這個約束關系放到代碼中實現,可能還需要實現跨服務的一致性檢查,或者實現周期性觸發清理數據的任務。我們可以通過類似于SchemeSpy這樣的工具來分析數據庫表之間的依賴關系。
3)共享靜態數據。例如,國家、部門之類的數據都是各個微服務之間經常使用的,這些數據的特征是不會經常變化,而且通用性高。這些數據在微服務劃分之后該如何處理呢?
方法一:我們可以為每個微服務復制一份這樣的數據,但這會導致數據的一致性問題。
方法二:把共享的數據放入代碼之中,如放在屬性文件中,或者簡單地放在一個枚舉中,但數據一致性問題仍然存在。
方法三:把這些靜態數據放在一個單獨的服務中。
4)共享數據。如果不同的微服務都使用了同一張表,在這種情況下該如何分享?其實這種情況很常見:領域概念不是在代碼中建模,相反是在數據庫中隱式地進行建模。這里缺失的領域概念是客戶,因而我們需要提供一個新的服務。
5)共享表。與共享數據不同的是,不同的微服務也會使用同一張表,但兩者修改的部分不一樣,在這樣的情況下,我們可以把這張表拆分成兩張表,分別供兩個微服務使用。
6)實施拆分。通常,我們推薦先分離數據庫結構,然后對代碼進行拆分的方法。表結構分離之后,對于原先的某個動作而言,對數據庫的訪問次數可能會變多。這也是我們需要考慮的問題,這里涉及分布式事務的相關問題。
另外,先拆分數據庫但不分離代碼的好處在于,可以隨時選擇回退這些修改或是繼續,而不影響服務的任何消費者。
總之,將現有應用遷移成微服務架構的現代化應用,不應該通過從頭重寫代碼的方式實現;相反,應該通過逐步遷移的方式實現。在拆分的同時,需要同期配置服務治理平臺,完成服務發現、配置管理、日志管理、監控等內容。平臺和應用是可以考慮分開的,由團隊專門負責。
OpenStack 云計算
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。