如何透過分解和增量更改將單體遷移到微服務?
服務遷移不是一個小更改。你必須搞清楚它是否真的能解決你的問題,否則你可能會建立一個會殺死你的、亂糟糟的實體。單體有不同型別,其中一些可能是有效的,足以滿足業務需求。單體不是一個應該被殺死的敵人。微服務關乎獨立部署。有一些分解和增量更改模式可以幫助你評估並遷移到微服務架構。當你開始使用微服務時,你會意識到隨之而來的是一系列非常複雜的挑戰。所以不應該將微服務作為預設選擇。你得仔細考慮它們是否適合你。
本文基於Sam在倫敦QCon大會上的演講記錄,由Leandro Guimarães整理,並由Sam審閱。
在倫敦QCon大會上,我談到了單體分解模式以及我們如何達成微服務。我喜歡把它們比作令人討厭的水母,因為它們是一種亂糟糟的實體,會刺痛甚至可能殺死我們。這在通常的企業微服務遷移中很常見。
許多組織正在經歷某種數字化轉型。隨便看下當前的任何數字化轉型,我們都會發現微服務的身影。我們知道,數字化轉型是一件大事,因為現在任何機場候機室都有大型IT諮詢公司的廣告推銷數字化轉型,包括德勤、DXC、埃森哲等公司。微服務非常流行。
不過,在談及微服務時,我關注的是結果,而不是我們用來實現它們的技術。我們選擇微服務架構的原因有很多,但我反覆提到的一個原因是其獨立部署的屬性。有一個功能,一個我們想要改變系統行為的更改。我們想要儘快實現這個更改。
將微服務架構與單體做下比較。我們認為,單體是一個單一的、無法透視的塊,我們無法對它作出任何更改。單體被認為是我們生活中最糟糕的東西,是難以擺脫的沉重負擔。我認為這非常不公平。最終,“單體”一詞在過去兩三年裡取代了我們之前使用的“遺留問題(legacy)”一詞。這是一個根本性問題,因為有些人開始將單體視為遺留問題,是需要移除的東西。我認為這非常不合適。
單體的型別
單體有多種形式和規模。在討論單體應用程式時,我主要是將單體作為部署單元來討論。考慮下經典的單體,它是將所有程式碼打包在單個程式中。它可能是Tomcat中的一個WAR檔案,也可能是一個基於PHP的應用程式,所有程式碼都打包在一個可部署單元中,該單元會與資料庫通訊。
這種單體型別可以看作是一個簡單的分散式系統。分散式系統是由多臺透過非本地網路相互通訊的計算機組成的系統。在這種情況下,所有的程式碼都打包在一個程式中,重要的是,所有資料都儲存在於一個執行在不同機器上的大型資料庫中。把所有資料都放在一個資料庫中,將來會給我們帶來很多痛苦。
我們還可以考慮下單程式單體的一種變體,稱為模組化單體。這種模組化單體使用了關於結構化程式設計的前沿思想(誕生於20世紀70年代初,幾十年後,我們中的一些人仍在努力掌握這些思想!)。如圖2所示,我們將單程式單體應用程式分解為模組。如果我們正確地劃分了模組邊界,我們就可以獨立地處理每個模組。但是,本質上,部署過程仍然是靜態連結的方法:我們必須連結好所有模組才能進行部署。比如一個Ruby應用程式,它由許多GEM檔案、NuGet包或透過Maven組裝的JAR檔案組成。
雖然我們仍然是單體部署,但模組化單體有一些顯著的好處。把程式碼分解成模組確實可以讓我們在一定程度上獨立地完成工作。它可以方便不同的團隊一起工作,並處理系統的不同方面。我認為這是一個被嚴重低估的選項。這其中存在的問題是,人們往往不善於定義模組邊界——更確切地說,即使他們擅長定義模組邊界,他們也不擅長保持這些邊界。遺憾的是,結構化程式設計或模組化的概念往往會遭遇“泥球”問題。
對於我服務過的許多組織來說,使用模組化單體比使用微服務架構會更好。在過去的三年裡,我對我一半的客戶說過:“微服務不適合你。“有些客戶甚至聽了我的話。對於它們中的許多來說,一種可以定義模組邊界的好方法就足以滿足它們的需要。他們可以得到一個比較簡單的分散式系統,以及一定程度上獨立、自主地工作。
模組化單體也有變體。圖3看起來有點奇怪,但這是我多次提出的建議,特別是對於初創公司,我通常認為,他們最好不要著急上微服務。如圖3所示,我們使用了模組化單體,並將後臺單個的整體資料庫進行了分解,這樣就可以單獨儲存和管理每個模組的資料。
雖然這看起來很奇怪,但歸根結底這是一種對沖架構。人們認識到,分解單體架構時最困難的工作之一是處理資料層。如果我們能提前設計好與這些模組相關聯的獨立資料庫,以後遷移到單獨的微服務就會更容易。如果我正在處理模組C,我對與模組C關聯的資料具有完全的所有權和控制權。當模組C變成一個單獨的服務時,遷移它應該會更容易。
當我還在ThoughtWorks工作時,我的一位老同事Peter Gillard-Moss第一次向我展示了這種模式。這是他為我們正在開發的一個內部系統設計的。他說,“我覺得這能行。我們不確定我們是否想要提供服務,所以也許它應該是一個單體。”
我說,“試一試。看看會發生什麼。“大約6年過去了,去年我和Peter談過,ThoughtWorks仍然沒有改變架構。它仍然執行得很歡快。他們讓不同的人處理不同的模組,即使是在這個級別上將資料分離開來,也給他們帶來了巨大的好處。
現在,我們來看看最糟糕的單體——分散式單體。我們的應用程式程式碼現在執行在彼此通訊的獨立程式上。不管出於什麼原因,我們都必須將整個系統作為一個單元同步部署。經常,這種情況的出現是因為我們弄錯了服務邊界。我們將業務邏輯胡亂地放在了不同的層上。我們沒有遵從關於耦合和內聚的要點,現在,我們的結賬邏輯分佈在服務棧中15個不同的地方。我們要做任何工作,都必須協調多個團隊。如果組織中存在大量的橫切更改,通常表明組織邊界或服務邊界定義的不對。
分散式單體的問題在於,它本質上是一個更加分散式的系統,但是對於所有相關的設計、執行和操作挑戰,我們仍然需要單體需要的那些協調活動。我想線上部署,但我不能。我必須等你完成更改,但你也完不成,因為你在等別人。現在,我們一致同意:“好吧,7月5日,我們將一起上線。每個人都準備好了嗎?三、二、一,部署。“當然,一切都很順利。對於這類系統,我們從來沒有遇到過任何問題。
如果一個組織有一位全職的釋出協調經理或這方面的其他職位,那麼他們可能有一個分散式單體。協調分散式系統的同步部署一點都不好玩。我們最終會付出更高的更改成本。部署的範圍會大很多,可能會有更多的地方出錯。這種難以避免的協調活動,不只存在於釋出活動中,而且存在於一般部署活動中。
稍微看下精益生產的內容就會發現,減少交接是最佳化生產力的關鍵。等待別人為我做點什麼只會產生浪費。這會導致生產力瓶頸。為了更快地交付軟體,減少交接和協調非常關鍵。遺憾的是,分散式單體往往會創造出不得不進行協調的環境。
有時,我們的問題不在於服務邊界在哪裡。有時,它完全是始於我們開發軟體的方式。有些人從根本上誤解了釋出序列。釋出序列一直被認為是一種治療性的釋出技術,而不是一種進取性的活動。我們會選擇像釋出序列這樣的東西來幫助組織轉向持續交付。釋出序列的概念以定期為基礎,也許每四周,軟體的所有部分已經準備就緒。如果軟體還沒有準備好,它就會被推遲到下一個釋出序列。對許多組織來說,這是向前邁出了一步。我們應該縮小發布序列之間的間隔,最終完全消除。然而,有太多的組織在採用了釋出序列就再也沒有繼續前進。
當若干團隊都朝著同一個釋出序列而努力時,所有已經準備好的軟體都會在這個釋出序列中交付——突然之間,我們會一次性部署大量的服務。這是真正的問題所在。當實踐釋出序列時,最重要的一件事是,至少要將這些釋出序列分解,使它們成為團隊釋出序列。允許不同的團隊安排自己的釋出序列。最終,我們應該拋棄這些序列。它們應該只是邁向持續交付的一個步驟。
遺憾的是,一些營銷敏捷的優秀成果已經將釋出序列作為交付軟體的最終方式。我們知道他們已經這麼做了,因為許多公司組織裡掛著的SAFe圖解上都印著“釋出序列”的字樣。這不是好事。不管是對於SAFe,還是你遇到的任何其他問題,釋出序列始終都是一種補救技術,是腳踏車的輔助輪。我們應該向著持續交付繼續前進。問題是,如果我們使用這些釋出序列時間太長,最終的架構就會是一個分散式單體,因為我們已經習慣了將所有服務部署在一起。要注意這一點。這可能不會在一夜之間發生。我們可以從支援獨立部署的架構開始,但如果我們使用釋出序列太久,我們的架構就會開始圍繞這些釋出實踐聚合在一起。
歸根結底,分散式單體是一個問題,因為它同時具有分散式系統的所有複雜性和單個部署單元的缺點。我們應該跨越它,尋找更好的工作方式。分散式單體是一件很棘手的事情,關於如何處理這種情況的建議已經有很多。有時,正確的答案是將其合併回單程式單體。但是,如果現如今我們有一個分散式單體,最好的辦法是弄清楚為什麼我們會有這樣的單體,並且在新增任何新服務之前,著手讓架構中的某些部分可以獨立部署。在這種情況下,新增新服務很可能會增加我們開展工作的難度。
2
如何將單體遷移到微服務架構
我們使用微服務架構是因為它具有獨立部署的特性。我們希望能夠在不改變其他任何東西的情況下將服務的更改部署到產品中。這是微服務的黃金法則。在演講或文章中,這似乎很容易。在現實生活中,要做到這一點要困難得多,尤其是考慮到大多數人並非從零開始。絕大多數人都覺得他們的系統太大了,想把它分成更小的部分。他們想知道從哪裡開始。
領域驅動設計(DDD)有一些很好的方法可以幫助我們找出服務邊界。當與研究微服務遷移的組織合作時,我們通常是從在現有的單體應用程式架構上執行DDD建模練習開始。我們這樣做是為了弄清楚單體應用內部發生了什麼,並從業務域的角度確定工作單元。
儘管單體看起來像一個巨大的盒子,但當我們應用DDD,並將邏輯模型投射到該單體上時,我們意識到,其內部被組織成訂理、PDF渲染、客戶端通知等內容。雖然程式碼可能沒有圍繞這些概念進行組織,但從使用者或業務領域模型的角度來看,這些概念存在於程式碼中。這些業務領域的邊界(DDD中通常稱為“有界上下文”)就成為我們分解的單元,原因我這裡就不展開討論了。
首先要做的是問下從哪裡開始,什麼事情可以優先處理,我們的工作單元是什麼。在圖5所示的初始單體中,我們有訂理、發票和通知。DDD建模練習將使我們瞭解它們之間的關係。但願我們能得出一個有向無環圖,來描述這些不同功能之間的依賴關係。(如果我們得到是一個依賴關係的迴圈圖,我們就得做更多的工作。)我們可以看到,在這個單體中,有很多東西都依賴於向客戶傳送通知的能力。那似乎是領域的核心部分。
3
檢查點:對於我遇到的問題,微服務是合適的解決方案嗎?
我們可以開始問問題了,比如我們應該先提取什麼。我可以完全從這個角度來看問題。我們可能會看到,通知被很多功能使用——如果微服務更好,那麼提取系統中很多部分都在使用的東西將使更多的東西變得更好。也許我們應該從那裡開始。但是,看看所有的入站依賴關係。因為有太多的部分需要通知功能,所以我們很難將其從現有的單體架構中剝離出來。在這個單體系統中,像結賬或訂理之類的概念似乎更加獨立。它們可能是更容易分解的東西。決定從哪一部分開始,從根本上講是一種漸進式分解方法。
首先要記住,單體並不是敵人。我希望大家都好好地思考一下。人們將任何單體系統都視為問題。在過去幾年裡,我看到的最令人擔憂的事情之一是,微服務現在似乎成了許多人的預設選擇。
有人可能記得一句老話:“沒有人會因為購買IBM產品而被解僱。”意思是,因為其他人都在買IBM產品,你也可以買——如果你買的東西不適合你,那也不是你的錯,因為大家都在這麼做。你沒必要冒險。現在每個人都在做微服務,我們也面臨同樣的問題。每個人都吵著要做微服務。這對我很有好處:我寫關於該主題的書,但對你可能不是好事。
從根本上說,這取決於我們想要解決什麼問題。我們想要達到而在當前的架構下無法達到的目標是什麼?也許微服務是答案,或者其他什麼東西才是答案。理解我們想要達到的目標至關重要,否則,我們將很難確定如何遷移我們的系統。我們正在做的事情將改變我們分解系統的方式,以及我們如何確定工作的優先順序。
微服務遷移不像一個開關,沒有開/關切換。這更像是轉動一個旋鈕。在採用微服務的過程中,我們轉動一下旋鈕,增加一兩個服務。我們想看看一個服務如何發揮作用,它是否提供了我們需要的東西,是否解決了我們的問題。如果是,而且我們也滿意,我們就可以繼續轉動旋鈕。
不過,我看到很多人都會轉動旋鈕,增加500項服務,然後插上耳機,檢查音量。這是讓鼓膜破裂的好方法。我們不知道我們將要面對什麼問題,那些問題在開發人員的膝上型電腦上碰不到。它們會在生產環境中出現。當我們從提供一個單體系統轉為一次性提供500個服務時,所有的問題都會同時出現。不管我們最後是提供一項、兩項還是五項服務,還是像Monzo那樣擁有800項或1500項服務,我們都必須從一個小轉變開始。我們需要選擇一些服務來啟動遷移。讓它們在生產環境中執行,積累經驗,並儘快把這種經驗付諸實踐。透過逐步調整,以漸進的方式建立和釋出新的微服務,我們可以更好地發現和處理出現的問題。每個專案將要面對的問題都會有所不同,這取決於許多不同的因素。
我們想要從單體系統中提取一些功能,讓它與單體系統的剩餘部分通訊並整合,並且要儘快完成。我們不想再進行大爆炸式的重寫了。我們過去是每年向使用者釋出軟體,因為有一個為期12個月的視窗期,所以我們可以這樣說:“現有的系統太糟糕了,現在已經無法使用了,但是我們還有12個月的時間來釋出下一個版本。如果我們努力,完全可以重寫系統,我們不會再犯過去犯過的錯誤,現有的功能一個都不會少,而且還會有更多的新功能,一切都會很好。”
當每年釋出一次軟體時,我們從來沒有那樣做。當人們期望每月、每週或每天釋出軟體時,我不知道該如何證明其合理性。套用Martin Fowler的話來說,“如果你要進行大爆炸式的重寫,你唯一能確定的就是大爆炸。”我喜歡動作片中的爆炸場面,但不喜歡我的IT專案裡出現這種情況。我們需要從不同的角度思考如何做出這些更改。
4
部署來自單體的第一個微服務
我是架構增量演進的忠實擁護者。我們不應該認為我們的架構是一成不變的。我們需要有一些模式來幫助我們以漸進的方式向微服務轉變。
我們首先看下應用程式模式Strangler Fig,它以一種植物命名,這種植物在樹冠上生根,然後卷鬚向下纏繞在樹幹上。絞殺榕(strangler fig)靠自身無法爬到林冠層以獲得足夠的陽光,所以它不像普通樹木一樣從一棵小樹苗慢慢長大,而是包裹在現有的植物體上。它依賴於現有的樹的高度和力量。隨著時間的推移,這些絞殺榕成長起來,變得越來越大,能夠獨立生存了。如果下面的樹死了,腐爛了,就只剩下絞殺榕和一根空心的柱子。這些東西看起來就像蠟滴在其他樹上——看起來真的很令人不安。
但是作為應用程式遷移策略的一種模式,這種思想是有用的。我們找一個現有的系統(它完成我們想要它做的所有事情,即現有的單體應用程式),然後開始圍繞它封裝出我們的新系統。在這裡,就是我們的微服務架構。實現Strangler Fig應用程式有兩個關鍵。第一個是資產捕獲,即確定把哪些功能遷移到微服務架構的過程。然後我們需要進行轉接。以前對於單體應用程式的呼叫得轉接到新功能上。如果功能沒有遷移,呼叫就不需要轉接;非常簡單。
有些人對如何轉移功能感到困惑。如果我們真的夠幸運的話,也許可以簡單地複製程式碼。如果結賬服務的程式碼在單體程式碼庫中一個叫“結賬”的漂亮盒子中,我們就可以剪下並貼上到新服務中。我認為,如果程式碼庫是這種狀態,那你可能不需要任何幫助。更大的可能是,我們將不得不快速瀏覽系統,設法收集所有與結賬相關的程式碼。我們可能會做一些重構前的活動。也許我們可以重用這些程式碼,但在這種情況下,那將是複製貼上,而不是剪下貼上。我們想把這個功能留在這個單體應用中,原因我將在後面討論。更常見的情況是,人們會進行一些重寫。
實現Strangler Fig的方法有很多種。讓我們來看一種簡單的方法。
假設我們有一個基於HTTP的單體系統。這可能是一個無頭應用程式。我們可以在使用者介面的後臺使用API Boundary攔截呼叫。我們需要的是可以將呼叫重定向的東西,因此,我們將使用某種HTTP代理。對於這類架構,HTTP協議非常有效,這是因為它非常適合透明地重定向呼叫。透過HTTP發起的呼叫可以被轉接到許多不同的地方。有很多軟體可以幫你做到這一點,而且非常簡單。
首先要做的是,在上游流量和下游單體系統之間放置一個代理,別的什麼都不用做。我們將把這個代理部署到生產環境中。此時,它還沒有轉接任何呼叫。我們可以看下它在生產環境中是否有效。我們要擔心的一件事是網路質量,因為我們增加了一個網路躍點。通常是直接呼叫單體系統,但現在透過我們的代理。在這種情況下,延遲是殺手。透過代理轉接只會給現有的呼叫增加幾毫秒的開銷——少於10毫秒就很棒。如果額外增加一個網路躍點增加了200毫秒的延遲,我們就需要暫停微服務遷移,因為我們還有其他需要首先解決的大問題。
準備好代理之後,我們接下來將處理新的結賬服務。我們將其部署到生產環境中。即使它功能還不全,也沒什麼問題,因為它還沒有被使用。我們要在腦海中將部署到生產環境和使用這兩個概念分開。開始採用微服務後,我們希望定期地將功能部署到生產環境中,以確保我們的部署機制能夠正常工作。在新增功能時,我們可以單獨測試新服務。我們還沒有把它釋出給使用者,但它已經在生產環境中了。我們可以將它連線到我們的儀表板上,確保日誌聚合正常,或者做其他我們想做的事。
關鍵是我們只對一個服務進行操作。我們甚至可以將那個服務的提取過程分解為許多小步驟:建立服務框架、實現方法、在生產環境中測試它,然後部署釋出版本。準備就緒之後,當我們認為新實現已經等同於舊系統時,我們只需重新配置代理,將呼叫從舊的單體功能轉到新的微服務。
事到如今,你可能認為現在應該從這個單體中刪除舊功能。先別這麼做!如果新建立的微服務在生產環境中出現問題,我們有一種非常快的補救技術:我們只需還原代理配置,將流量轉到原功能所在的單體上。不過,要想實現這一點,我們必須考慮資料的作用——這點我們稍後討論。
我們希望,將這個功能提取成微服務是一種真正的重構,改變程式碼的結構而不是行為。在功能上,微服務應該等同於單體中的同一功能。我們應該能夠在它們之間切換,直到微服務正常工作為止。
如果我們想要保留切換的能力,那麼在遷移完成、我們不再需要這種切換能力之前,我們就不應該新增新的功能或更改現有的功能。
在很多情況下,這項簡單的Strangler Fig技術都出奇的好。這個例子使用了HTTP,但是我也看過使用FTP的情況。我已經用訊息攔截器做到了這一點。我在上傳固定檔案時就這麼做了:我們插入固定檔案,在新服務中剔除我們希望去掉的內容,然後把剩下的內容傳遞下去。
5
使用“抽象分支“模式來逐步完成單體遷移
Strangler Fig對於結賬或訂理等功能非常有效,這些功能在我們的呼叫堆疊中處於更高的位置,如圖7所示的依賴關係圖。但是,進入單體系統的呼叫沒有哪個是為了獲得像忠誠獎勵積分或給客戶傳送通知這樣的能力。進入單體的呼叫是“下訂單”或“付款”。只是作為這些操作的副作用,我們可能會獎勵積分或傳送電子郵件。因此,我們無法在單體系統的外圍攔截對忠誠獎勵積分或通知的呼叫。那是在單體系統內部完成的。
假設我們要把通知功能提取出來。我們必須提取這塊功能,並用一種增量的方式攔截這些入站連結,這樣我們就不會破壞系統的其餘部分。
一種名為“抽象分支”的技術可以很好地完成這項工作。在基於主幹的開發環境中,抽象分支是一種經常討論的模式,這是一種很好的軟體開發方式。在這種情況下中,抽象分支作為一種模式也很有用。我們在現有的單體系統中建立了一個空間,同一功能的兩種實現可以在其存。在很多方面,這是里氏替換原則的一個例子。這是對完全相同的抽象做的一個獨立實現。對於本例,我們將從現有程式碼中提取通知功能。
圖8:用於遷移到一個微服務的抽象分支
通知程式碼散佈在我們的系統中。我們要做的第一件事是為新服務收集所有這些程式碼。我們將把服務隱藏在抽象點後面。我們希望結賬程式碼和訂單程式碼透過一個明確的抽象點來訪問這個功能。起初,我們有一個通知抽象的實現——它封裝了單體中當前所有與通知相關的功能。我們的所有呼叫——到SMTP庫、到Twilio、傳送SMS——都被打包到這個實現中。
此時,我們所做的只是在程式碼中建立了一個很好的抽象點。我們可以停了。我們已經釐清了我們的程式碼庫,並使其更容易測試,這已經是改進了。這是一種很好的老式重構。我們也創造了一個機會來更改結賬或訂單使用的通知功能的實現。我們可以用幾天或幾周的時間來完成這項重構工作,同時做一些其他的事情,比如實際釋出特性。
接下來,我們開始建立通知服務的新實現。這可以分成兩個部分。我們已經在單體中實現了新介面,但這只是呼叫另一部分(新建的通知微服務)的客戶端程式碼。部署這些實現很安全,因為它們還沒有被使用。我們更頻繁地整合程式碼,減少合併工作,並確保一切工作正常。
一旦單體內部呼叫新服務的程式碼和單體外部的通知服務可以正常工作,我們所需要做的就是切換我們正在使用的抽象實現。我們可以使用特性開關、文字檔案、專用工具,或者任何我們希望使用的方式。我們還沒有刪除舊功能,所以如果有問題,我們可以輕鬆切回舊功能。同樣,這個服務的遷移被分解成許多小步驟,我們試圖透過所有這些步驟儘快將其部署到生產環境。
一切工作正常之後,我們就可以選擇清理程式碼了。如果不再需要這個功能,我們可以刪除其特性標識,甚或刪除舊程式碼。現在刪除舊程式碼很容易了,因為我們已經花了一些時間將所有程式碼整理好。我們刪除了那個類,它消失了。我們把單體變小了,每個人都對自己感到滿意。
6
並行執行驗證微服務遷移
就程式碼重構而言,我強烈推薦Michael Feathers的著作《修改程式碼的藝術》。他對遺留程式碼的定義是沒有測試程式碼的程式碼。關於如何在不破壞現有系統的情況下,在程式碼庫中找出並建立這些抽象,這本書提供了很多好主意。即使你不使用微服務,僅僅建立這個抽象點就可能會使你的程式碼處於更好、更可測試的狀態。
我已經強調過,不要太早刪除舊實現。保留兩種實現有很多好處。它為我們如何部署和上線軟體提供了有趣的方法。當呼叫進入抽象點時,它可以觸發對這兩個實現的呼叫。這叫做並行執行。這可以幫助我們確保新的微服務實現功能上等價。我們執行該功能的兩個副本,然後比較結果。
要做這個比較,只需執行這兩個實現並比較結果。我們必須指定其中之一作為真相來源,因為我們不希望把兩者串聯起來:例如,在傳送通知時,我們只想傳送一封郵件,結果卻發了兩封。並行執行是一種實用而直接的實時比較,不僅是功能等價性的比較,而且包含可接受的非功能等價性比較。我們不僅要測試是否建立了正確的電子郵件,並將其傳送到正確的虛擬SMTP伺服器,而且還要測試新服務的響應速度是否同樣快,或者錯誤率在可接受範圍之內。
通常,我們信任舊的功能實現,並使用其結果。我們將它們並行執行一段時間,如果新實現提供了可接受的結果,我們最終將處理掉舊的。
GitHub可以幫我們做這件事。他們建立了一個名為GitHub Scientist的庫,這是一個很小的Ruby庫,用於封裝不同的抽象並對它們進行評分。在重構應用程式中的關鍵程式碼路徑時,我們可以使用它來進行實時比較。GitHub Scientist已經被移植到了很多不同的語言上,令人費解的是,Perl有三種不同的移植:顯然,在Perl社群,並行執行是一件很重要的事情。關於如何在應用程式內部並行執行,已經有很多很好的建議。
7
將部署與釋出分離:根本性的改變
從根本上說,我們需要將部署的概念與釋出的概念分離開來。傳統上,我們認為這兩種活動是一回事,部署軟體和向生產環境使用者釋出軟體是一回事。這就是為什麼每個人都害怕生產環境會發生什麼事情,這就是生產環境成為一個封閉環境的原因。
我們可以把這兩個概念分開。將某樣東西部署到生產環境中與將它釋出給我們的使用者是不一樣的。這個想法是人們現在所說的“漸進式交付”的基礎,這是一個涵蓋了一系列不同技術的總稱,包括金絲雀釋出、藍/綠部署、抹黑啟動等。我們可以快速推出軟體,但不必向任何客戶公開。我們可以把它放到生產環境中,在那裡測試,然後自己承受出現的任何問題。
如果我們將部署與釋出分開,那麼部署的風險就會小很多。我們就會更加勇敢地進行更改。我們將能夠更頻繁地釋出,而且釋出的風險將更低。
RedMonk聯合創始人James Governor在公司的部落格上對漸進式交付做了很好的闡述。該文探討了漸進式交付,其中最重要的結論是,主動部署與主動釋出不是一回事,並且你可以控制釋出活動如何發生。
8
用微服務方法遷移簡單的資料訪問
我們將現有的單體應用程式和資料鎖定在系統中,如圖9所示。我們已經決定提取結賬功能,但是它需要訪問資料。
圖9:從新服務訪問舊資料
選項一是直接訪問單體的資料。如果我們仍然在測試並在單體中的結賬功能和微服務裡的結賬功能之間進行切換,我們會希望這兩種實現之間具有資料相容性和一致性,這種方式可以保證這一點。這在短時間內是可以接受的,但它違背了資料庫的黃金規則之一:不共享資料庫。這不是可以長期依賴的東西,因為它會導致根本性的耦合問題。我們希望保持獨立部署的能力。
圖10:從新服務直接訪問舊資料
如圖10所示,我們有一個Shipping服務和資料庫,我們允許其他人訪問我們的資料。我們已經向外部公開了內部實現細節。這使得Shipping服務的開發人員很難知道哪些內容可以安全地更改。哪些資料要共享,哪些資料要隱藏,並沒有做區分。
20世紀70年代,David Parnas提出了“資訊隱藏”的概念,我們就是以此為基礎考慮模組分解。我們希望在模組或微服務的邊界內隱藏儘可能多的資訊。如果我們建立一個定義良好的服務介面來共享資料,而不是直接公開資料庫,那麼這個介面就讓Shipping服務的開發人員可以明確知道這個契約以及他們可以向外界公開什麼。只要遵守該契約,開發人員就可以在Shipping服務中做任何他們想做的事情。也就是說,這些服務可以獨立演進和開發。不要直接訪問資料庫,除非是在極其特殊的情況下。
拋開直接訪問,我們有兩種選擇:要麼訪問別人的資料,要麼儲存自己的資料。對於這個例子,假如我們已經確定新開發的結賬微服務已經足夠好,可以作為我們的真相來源。
此時,如果我們想要使用別人的資料,那麼這可能意味著資料屬於單體,我們必須向單體請求資料。我們在單體上建立某種顯式的服務介面(在我們的示例中是一個API),透過它獲取我們想要的資料。
圖11:新開發的微服務使用單體顯式提供的一個服務介面
我們是結賬服務,而不是訂單服務,但我們可能需要訂單資料。訂單功能存在於單體中,因此我們將從那裡獲取資料。這樣來說,我們需要在單體上定義服務介面以公開不同的資料集,而且,在這樣做時,我們可以看到其他實體從單體中顯現出來。我們可能會發現,訂單服務正等待著從單體中噴發出來,就像《異形》中的異形幼體,在電影中,單體是由John Hurt扮演的,它會死去。
另一種選擇是儲存服務自己的資料——在本例中,是單體資料庫中的結賬資料。至此,我們必須把資料移到一個結賬資料庫,這真的很難。從現有系統(尤其是關聯式資料庫)中提取資料會帶來很多麻煩。我們將看一個簡單的例子,看看它帶來的挑戰。我將帶大家深入瞭解一下,如何處理連線。
9
最後的挑戰:處理連線操作
圖12描述了一個現有的、線上銷售光碟的單體應用程式。(你可以看出我這個例子已經用了多久了。)Catalog功能知道某些東西多少錢,並將資訊儲存在Line Items表中。Finance功能管理我們的財務交易,並將資料儲存在Ledger表中。我們要做的其中一件事是,生成一個每週銷量前10的專輯列表。在這種情況下,我們需要做一個簡單的連線操作。我們從Ledger表上查出10個最暢銷的。我們根據行和其他東西來限制這個查詢。這樣我們就能得到ID列表了。
圖13:線上銷售光碟的微服務架構
在進入微服務領域後,我們需要在應用層執行連線操作。我們從Finance資料庫中提取財務交易資料。關於我們出售的物品的資訊則存在於Catalog資料庫中。為了生成銷量前10名列表,我們必須從Ledger表中提取最暢銷商品的ID,然後轉到Catalog微服務,查詢所銷售商品的資訊。我們過去在關係層中執行的連線操作轉移到了應用程式層。
延遲可能會變得令人震驚。現在,我不是做一個一次往返的連線操作,而是要呼叫Finance服務獲取銷量前10的ID,然後呼叫另一個Catalog服務請求這10個ID的資訊,然後Catalog服務從Catalog資料庫中取得這些ID,然後我們才得到響應。圖13說明了這個過程。
圖14:微服務架構會導致更多的躍點和延遲
我們還沒有涉及到像缺乏資料完整性這樣的問題(在這種情況下,關係型資料庫如何實現引用完整性)。
10
小結
如果你想深入研究諸如處理延遲和資料一致性之類的問題,我在《從單體到微服務》一書中進行了深入的闡述。
無論你是否決定繼續自己的微服務遷移之旅,我都建議你仔細考慮下,自己正在做什麼以及為什麼要這樣做。不要把注意力都放在建立微服務上。相反,你要清楚自己想要達到的結果。你認為微服務會帶來什麼結果?專注於這一點——你可能會發現,你可以在不進入複雜的微服務世界的情況下實現同樣的結果。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69995861/viewspace-2761976/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 使用SpringCloud將單體遷移到微服務SpringGCCloud微服務
- 如何遷移到微服務和事件溯源EventSourcing微服務事件
- 如何從複雜單體應用快速遷移到微服務?微服務
- 在將單體遷移到微服務之前需要了解的模式 - Abhishek微服務模式
- 從單體遷移到微服務的十二種方法微服務
- 透過MySQL Workbench 將 SQL Server 遷移到GreatSQLMySqlServer
- Wix如何零停機將將2000個微服務遷移到多叢集Kafka?微服務Kafka
- 經驗分享:將微服務遷移到Spring WebFlux - allegro.tech微服務SpringWebUX
- 如何將 CentOS遷移到 AlmaLinux?CentOSLinux
- 案例:微服務從Java/SpringBoot遷移到Golang微服務JavaSpring BootGolang
- 微服務閘道器Zuul遷移到Spring Cloud Gateway微服務ZuulSpringCloudGateway
- WinUI遷移到即將"過時"的.NET MAUI個人體驗UI
- 我如何將部落格遷移到 Kubernetes(上)
- 我如何將部落格遷移到 Kubernetes(下)
- 如何透過 Serverless 提高 Java 微服務治理效率?ServerJava微服務
- 如何將您的 Eventlet 專案遷移到 Asyncio
- 將nodejs遷移到D盤NodeJS
- 從微服務遷移到工作流的經驗之談微服務
- 透過skaffold快速部署微服務微服務
- MySQL5.7 透過邏輯備份遷移到GreatSQL注意事項MySql
- 如何在不重構的情況下將單體拆分成微服務?微服務
- 單體和微服務幽默新解圖片微服務
- Python 將所有 Bug 遷移到 GitHub 中PythonGithub
- 架構之:微服務和單體服務之爭架構微服務
- 如何拆分大型單體系統為微服務微服務
- 從單體架構遷移到 CQRS 後,DDD 並不可怕架構
- 將 flutter_web 遷移到 flutter1.9+FlutterWeb
- 將maven、gradle倉庫遷移到d盤MavenGradle
- Zenlayer如何將萬臺裝置監控從Zabbix遷移到Flashcat
- 如何將物理機Windows系統遷移到VMware虛擬機器?Windows虛擬機
- [譯] 將專案遷移到 Yarn 然後又遷回 npmYarnNPM
- 如何透過 Serverless 技術降低微服務應用資源成本?Server微服務
- 將ZooKeeper遷移到Kubernetes的新方法 - hubspot
- SpringBoot透過refresh-ahead caching加速微服務效能Spring Boot微服務
- 如何將複雜專案分解為可管理任務?
- Lyft如何透過DevOps提升擴充套件微服務的生產力? - Garrettdev套件微服務
- 從過時的 Windows 機器遷移到 LinuxWindowsLinux
- 單體巨石、微服務和SOA關係與區別微服務