與單體式應用分手的7個正確姿勢
本文由公眾號EAWorld翻譯發表,轉載需註明出處。
作者:Troy Leland
譯者:白小白
原文:
原題:Breaking up with a Monolith
第0課:什麼時候提分手?
微服務時下大紅大紫。人們很容易迷失在決定使用“最先進的技術”這樣的興奮之中,把微服務這一行業“最佳”實踐當作是本公司的正確選擇,僅僅是因為有雪片一樣的文章描繪了這一架構在Netflix這樣的公司是如何的成功。
如果你既不想快速的擴張你的客戶基礎,也不想快速擴張你的工程團隊。那麼大刀闊斧的把系統拆分成更多的服務前,一定要三思而行,因為這也同時意味著系統複雜性的快速擴張。
話雖如此,把單體應用重構成微服務畢竟還是有很多的優點。還是強烈建議定期梳理系統中已知的服務以確保其滿足企業業務發展的需要。
僅僅是因為你的企業使用了Kubernetes或者有許多的後端服務專案(可能每個專案在README.md檔案中都有一段明確而簡短的說明)並不意味著能夠避免一些隱含的單體應用會給你的開發努力帶來問題。今天看起來足夠“微”的系統架構,明天可能就需要修修補補,因為業務是不斷增長的,產品也會不斷的變更。
到底什麼時候該把單體應用切成微服務呢?
如果你的服務有如下的特徵之一,那麼可能需要重新架構你的系統了:
1、沒有明確的所有者;或者有多個團隊對某個服務指手畫腳;
2、無數的外部依賴和向前依賴;
3、不支援多例項執行(可能因為服務是有狀態的);
4、某些特性很難測試;
5、部署成功率聽天由命(即使是小的變更也是如此);
凌晨4:54的大膽部署
Weave公司的分手實踐
在2017年的某個時刻,我們大無畏的CTO Clint Berry,責成每個研發團隊要確保其產品可以在2019年擁有4倍於當前客戶基數的需要的伸縮能力,並且不能有服務中斷。
我捋了捋我們所負責的專案,發現有一個關鍵服務很可能連2018年的增長需要都無法滿足。
我所在的公司主要為小型的業務單位(如牙醫)提供整合通訊平臺,其主要賣點之一就是自動訊息傳送(如預約提醒)。上面說到的那個關鍵服務就是用來規劃和傳送所有的自動化通訊的,從預約通知簡訊到生日祝福郵件,涵蓋了訊息的方方面面。
表面上看,這個服務本來就不該是單體應用。比如,這個服務有一段簡明的工作職責描述:“規劃和傳送自動化的訊息”。但服務本身並不需要實際的傳送任何訊息或郵件,而是透過其他微服務來完成這個任務。
然而仔細觀察的話,這個服務還是釋放出很多的危險訊號以致於不能簡單的拆解成微服務。這個服務本身需要執行很多的批次處理任務,這就使其橫向擴充套件方面受到制約。對這個服務的測試是困難和複雜的,即使是微小的變更也是如此。這個服務“知道的太多”,擁有大量的外部依賴。每次的部署都讓我緊張兮兮,有時甚至需要在凌晨3點把某個兄弟叫起來,只是為了確認一切安好。
這個服務目前為止工作良好。但是現在不拆,我就需要在接下來的一年裡提心吊膽。
第1課:遷移過程需要有專人負責
從單體向微服務架構遷移的重要性和嚴重性,常常被專案團隊成員所低估。需要有專人手持上方寶劍來為之奮鬥,讓公司優先考慮這一使命的達成而不是獲得更多的客戶案例,儘管後者可以更顯而易見的驅動業務的推進。
只有良好的願望是無法讓單體應用自然而然的變成設計良好的微服務的。不要對這一點存有錯誤的奢望。
現實中,我們需要解決很多的小型的遷移過程。這些過程可能耗時數月,在某些案例中,甚至結結實實的花費了超過一年的時間,這一現狀在今時今日仍舊在重演。制定規劃,小步快走,方為上策。
立刻、馬上完成遷移過程,並不需要成為每個人的頭號優先順序任務。但卻需要有專人負責確保後續的遷移過程始終有人維護,平衡其他的工作任務,並將此做為一個優先任務。
第2課:越簡單越好
部署、資料庫和虛擬機器置備
幾年前,所有的Weave後端服務都基於虛擬機器執行。建立一個微服務需要在開發端和運維端往來反覆,去置備一個滿足必要依賴條件的虛擬機器。部署服務過程是有點痛苦的,回滾操作尤其如此。即使是這樣,Weave還是積累了一堆服務。
理想情況下,向微服務架構遷移之前,開發者應該能夠容易的為服務置備其計算資源,安全無誤的部署服務,並且在某處安全的儲存服務的多副本資料,然而,要達到這樣的前提條件顯然不大現實。
從Weave的實踐看來,作為遷移的一部分,當需要建立一個微服務時,Kubernetes可以解決上述的大多數問題,讓事情變得容易許多。
隔離、拆分、重構與重寫
在告別單體式架構的同時,我們也面臨著巨大的誘惑,畢竟這是一個把所有業務邏輯從零開始重寫一遍的機會。
雖然有大量的個案證明,重寫是一個正確的選擇,但在引入了新的微服務,並且將所有的流量路由到新服務以前,我們仍然需要從整體上避免大的變更。遷移本身已經帶來了太多的變化 ,不要承擔不必要的複雜性。
遷移過程首先從單體應用中一些邏輯上相對獨立的部分開始,並將這些部分轉移為對應的微服務。這意味著在實際的遷移過程開始之前,做為準備工作,需要對單體架構做一些重構。
然後,我們對該部分的功能實現物理的隔離(通常也就是把原來的程式碼複製和貼上了一下)。然後再慢慢向新的微服務匯入使用者流量。
一旦我們確認了新建立的服務可以正常工作,就可以靈活的根據需要對服務進行重構以及重寫。在對業務邏輯做出重大的變更之前,我們會一直等待直到對遷移過程的平滑執行達到滿意,否則的話,遷移過程實際上遠未完成。
跟蹤
隨著服務的數量越來越多,除錯的難度也越來越大。一個請求可能依賴一堆的服務給出響應。精準的定位失敗,找到哪一個服務發生了問題,變得困難無比。
使用類似Jaeger這樣的工具來進行請求的跟蹤可以極大的改善對微服務的分析和測試。我們可以從整體上而不僅僅是在本地的上下文中觀察服務請求.Jaeger是我們用過的最棒的工具之一,傳統的日誌工具相形見絀。
如果不能進行某種方式的跟蹤,以處理分散式系統的複雜性,我寧願不和微服務架構打交道。因為我瞭解跟蹤的價值。
第3課:瞭解成功的標誌
從單體架構向微服務架構的遷移可能看起來簡單,但前行的路上充滿荊棘。
不妨這樣設想一下:你希望目前的開發者對此前的開發者留下的工作進行一個全新的架構設計。這將需要對公司數年前的系統擁有一定的知識儲備。
確實有一些遷移過程就像把程式碼拷來拷去一樣簡單,但多數情況下都會發生意外。鑑於這些不確定性的存在,瞭解以下兩點尤為重要:即何時遷移過程是成功的需要向前推進,而何時是失敗的需要退回到原來的位置。
這就意味著,在建立乾淨和嶄新的微服務程式碼庫之前很早的時間,就需要針對給定的單體架構開展微服務架構的遷移工作。
測試和測試計劃
鑑於我們已經確定不會進行任何大的重構工作,這使得我們有能力把所有的單元測試移植到新建立的微服務中,並且有把握不會出什麼大的紕漏。當然,在此過程中,所有的測試也需要保持原狀。單元測試對於安全的部署來說是個好東西,如果特定的程式碼段未經測試,我們會在遷移前把他們寫到單體架構中。
整合測試與實現依賴度不高,會更多的對重構後的系統進行行為驗證,這樣的處理方式更為合理。遺憾的是整合測試就像獨角獸一樣虛無縹緲,無法實現。
在向微服務遷移程式碼的過程中,如果沒有自動化的整合測試,我們將不得不求助於手工測試。儘管令人遺憾並且困難重重,但不失為通往成功的路徑之一。
度量
Prometheus 和 Graphite 也是成功之路上的關鍵工具,可以讓我們有能力收集度量指標。在向新服務匯入使用者流量的過程中,我們依賴這些指標來表徵沒有發生任何重要的意外的變更。正如單元測試可以確認某個特定功能執行的實際結果符合預期一樣,度量指標可以對整個系統完成這個工作。
為了讓度量行之有效,我們需要做兩個工作:一是將度量工作排在第一優先順序,二是確保新的度量指標相對於現有的指標從可用性的角度具有可比性。注意,從這個意義上講,建立具有可比性的指標要比建立正確的指標更加重要。
儘管度量是一個事後指標,僅在某些問題已經出現之後才顯現,在我們向越來越多的客戶引入變更的過程中,度量指標仍舊使我們受益良多。
第4課
承擔適當的風險,隨時準備回退
即使變更微小而明確,程式碼覆蓋率也很棒,部署這一過程仍舊存在內在的風險。
將單體應用重構為微服務架構的風險在於,大量的變化在同一時間發生,而發生變化的程式碼往往是當下的開發者所無法充分理解的遺留程式碼。
好在直到你已經做好準備向新的微服務匯入流量之前,完全可以從大體上讓原有的單體應用保持原樣不動並繼續執行。在此過程中,不斷的把單體應用中的一部分拆分出來成為新的服務。
最後,在編寫、測試以及部署了新的微服務之後,你將迎來宿命的冒險:向新服務匯入真實的使用者流量。
你需要綜合評估所能承受的風險以及每種推進策略實現的可行性,來決定最好的推進策略。
對相關策略的評估需要考慮回退過程是否會出現問題。如前所述,知道什麼時候成功十分重要,與此同等重要的是當意識到失敗的時候有能力回退到原來的狀態。當論及單體架構的遷移工作時,如果你的團隊成員建議“破釜沉舟,不留後路”的話,我建議你找到一條風險更低的路徑。
在努力重構自動訊息傳送這一單體應用的過程中,我們的團隊以這樣或那樣的形態評估瞭如下所有的策略。
轉移全部流量
對於我們所遇到的特定的問題來說,將所有的流量匯入到新的微服務是很容易的,但隨之而來的是大量的風險。一旦系統出現問題,錯誤的預約提醒將會以簡訊的形式永久記錄在案,讓我們的錯誤無所遁形。(舉個例子吧,如果成千上萬的預約提醒在凌晨4:30發給使用者的話,顯然不大理想,好在這樣的事情從未發生? ?♂️)
而處理一個簡單的CRUD應用,只需要在某個晚上部署上線一段時間,看是否能正常工作即可。此時的回退策略也很簡單,如果應用不能正常工作,就把流量切回單體應用即可。
透過負載均衡轉移少量流量
這種方式可能有效,但同時也帶來兩個問題。首先,我們並不真的需要一個單獨的框架來完成服務間的負載均衡。我們使用gRPC通訊,再弄一個負載均衡有點畫蛇添足。此外,我們需要對哪些請求呼叫了新的服務施加更多的控制,以便可以跟蹤到每個客戶的執行結果,而不是對請求過程隨機處理聽天由命。
功能開關
功能開關(Feature Flags)在我們的團隊用得很好,因為我們已經有了一個現成的框架進入到了Beta測試階段。我們可以對使用新服務的特定使用者開放某個特定功能,這讓我們有把握將功能安全的開放給更多的使用者,同時又可以在出問題時很容易的回滾操作。
冗餘釋出
我們所考慮採用的最後一個策略是實現冗餘釋出(Shadow Reads)。這一策略的理念是同時使用實際驗證可靠的單體應用和重構後的微服務來響應同一個請求,對比兩者的執行結果來驗證新的服務可以正常工作。如果出現了意外情況,就把錯誤記錄下來,並以單體應用的執行結果為準。
以這樣的方式,回滾策略和程式碼高度融合。我們可以一次性的對整個客戶群體進行測試(相對於功能開關的緩慢回滾顯然效率更高)。不足之處是實現這項工作需要花費更多的精力,同時也隱含更多的風險。
一段時間裡,這樣的策略對我們很有效,但後來我們轉而使用功能開關來實現更多的控制。
第5課:避免矯枉過正
前面說過,Weave曾經在虛擬機器上釋出服務,但轉向Kubernetes讓事情變得更容易。事實上,因為釋出一個新的服務是如此的容易,以至於Weave的開發團隊心血來潮就會釋出一個,這也是為什麼現在我們需要對數百個服務進行監控和維護的原因。看來有時太容易也不是件好事。
但大體上我還是認可Weave處理微服務架構的方式,從單體應用向微服務的遷移總是存在著矯枉過正的風險,人們會開發比謹慎評估所需更多的微服務。
我想這是Weave必須上的一課,我們也會靜觀事態的進展。然而,將如下的建議謹記於心可能仍舊重要:把單體應用拆分成幾個更小、定義更合理的單體應用,而不是幾十個過於細小的微服務,相對你的企業來說可能是更好的選擇。
第6課:清理自己程式碼
最後一課十分重要:只要程式碼清理工作沒完成,遷移工作就沒結束。
如前文所述,我們的團隊對於自動化訊息傳送服務的遷移工作耗時數月。因此,隨著微服務數量的增長,原有的單體應用的程式碼庫本應急遽縮水。
糟糕的是,道理都懂,但我並不總是勤於清理自己的程式碼,這帶來了很多不確定性。其他的團隊成員甚至我自己有時候都很難除錯一段程式碼,原因很可能是同樣的或者類似的程式碼段同時在兩個地方同時並存著。
這樣的情況實際發生過。當時一個團隊的新人花了數天的時間來修復一段已經癱瘓了一個月的程式碼。我得坦白,正是由於我沒有經常性的巡視程式碼並刪除舊的程式碼片斷才引發了這樣的困擾。
總結
過程艱辛,教訓良多,但從整體上來看,將單體應用打散為可維護性更好的微服務的過程還是令人愉悅的。
可以鼓掌了。
關於EAWorld:微服務,DevOps,資料治理,移動架構原創技術分享
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31562043/viewspace-2640108/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- redis應用系列一:分散式鎖正確實現姿勢Redis分散式
- 寫一個 iOS 複雜表單的正確姿勢iOS
- 掌握Redis分散式鎖的正確姿勢Redis分散式
- 「分散式」實現分散式鎖的正確姿勢?!分散式
- Redis的正確使用姿勢Redis
- Postman 正確使用姿勢Postman
- 學習軟體開發的正確姿勢
- 這才是實現分散式鎖的正確姿勢!分散式
- git commit 的正確姿勢GitMIT
- 玩轉 Ceph 的正確姿勢
- 開啟Git的正確姿勢Git
- laravel 使用 es 的正確姿勢Laravel
- java關流的正確姿勢Java
- 使用快取的正確姿勢快取
- Fragment commit 的正確姿勢FragmentMIT
- 面對祖傳屎山程式碼應該採用的5個正確姿勢
- Goland 開啟一個專案的正確姿勢GoLand
- 開發函式計算的正確姿勢 —— 爬蟲函式爬蟲
- 用Python解鎖“吃雞”正確姿勢Python
- 原始碼|使用FutureTask的正確姿勢原始碼
- 相容iphone x劉海的正確姿勢iPhone
- npm run dev 的正確使用姿勢NPMdev
- 限制UITextField字數的正確姿勢UI
- 使用 Java 8 Optional 的正確姿勢Java
- 解鎖 Redis 鎖的正確姿勢Redis
- MySQL 5.6建索引的正確姿勢MySql索引
- Python 操作 MySQL 的正確姿勢PythonMySql
- 解鎖redis鎖的正確姿勢Redis
- 演算法分析的正確姿勢演算法
- Java日誌正確使用姿勢Java
- 用 JavaScript 刷 LeetCode 的正確姿勢【進階】JavaScriptLeetCode
- 開發函式計算的正確姿勢——OCR 服務函式
- 【新夢想老師分享】分散式鎖的正確"姿勢"分散式
- Pwn掉智慧手錶的正確姿勢
- Spring Boot使用AOP的正確姿勢Spring Boot
- Flutter 錯誤捕獲的正確姿勢Flutter
- 使用 react Context API 的正確姿勢ReactContextAPI
- 實現Flutter彈窗的正確姿勢..Flutter