拆分實踐復(fù)盤:邊界判斷、SpringCloud落地與避坑指南)
以下是我對這次微服務(wù)拆分實踐的全部復(fù)盤和思考希望對正在做同樣決定的你有些參考價值。1. 拆之前的靈魂拷問你確定你的系統(tǒng)真的需要微服務(wù)嗎1.1 一次為了微服務(wù)而微服務(wù)的翻車經(jīng)歷幾個月前某公司一個日活量并不高、團(tuán)隊只有七八個人的項目組因為看到技術(shù)社區(qū)里都在聊SpringCloud就拍板把原本跑得好好的單體應(yīng)用拆成了十幾個微服務(wù)。服務(wù)拆完以后噩夢開始了。原本一次本地啟動只要十幾秒的應(yīng)用現(xiàn)在要同時啟動注冊中心、配置中心、網(wǎng)關(guān)和五六個業(yè)務(wù)服務(wù)光等全部起來就要將近十分鐘。線上一個簡單的用戶下單流程要經(jīng)過API網(wǎng)關(guān)、訂單服務(wù)、庫存服務(wù)、用戶服務(wù)、支付服務(wù)五跳轉(zhuǎn)發(fā)任何一個環(huán)節(jié)慢一點整個鏈路就超時。最離譜的是有一次因為訂單服務(wù)的一個配置項寫錯導(dǎo)致所有依賴訂單服務(wù)的模塊全部異常排查了半天才發(fā)現(xiàn)問題出在配置中心而不是代碼邏輯。這不是個例。我見過太多團(tuán)隊把用了SpringCloud當(dāng)成技術(shù)先進(jìn)的標(biāo)志卻忽略了微服務(wù)本質(zhì)上是一筆巨大的前期負(fù)債。拆之前沒人認(rèn)真算過這筆賬網(wǎng)絡(luò)開銷增加了多少、排障鏈路長了多少、發(fā)布流程復(fù)雜了多少、分布式事務(wù)怎么處理、團(tuán)隊有沒有能力維護(hù)一套完整的治理體系。1.2 微服務(wù)的收益曲線和成本曲線從來不是同步的很多人只看到了微服務(wù)的收益——獨立部署、獨立伸縮、故障隔離、團(tuán)隊自治——卻忽略了這些收益要到達(dá)一定規(guī)模之后才會顯現(xiàn)。小團(tuán)隊、小流量的系統(tǒng)拆成微服務(wù)收益幾乎為零成本卻是實打?qū)嵉摹N伊?xí)慣畫兩條曲線來幫助團(tuán)隊做判斷。一條是收益曲線隨著服務(wù)拆分粒度變細(xì)收益先上升但到了某個點之后開始下降因為服務(wù)之間的協(xié)作成本、運(yùn)維成本開始吃掉收益。另一條是成本曲線拆得越細(xì)基礎(chǔ)設(shè)施成本、監(jiān)控成本、溝通成本、排障成本就越高這條曲線幾乎沒有上限。這兩條曲線的交點就是最優(yōu)拆分粒度。對大多數(shù)中小團(tuán)隊來說交點遠(yuǎn)比想象中靠左。很多系統(tǒng)拆到二三十個服務(wù)的時候?qū)嶋H上已經(jīng)過了最優(yōu)點只是團(tuán)隊礙于面子不愿承認(rèn)。1.3 明確拆分的啟動信號而不是跟風(fēng)那到底什么時候才應(yīng)該考慮拆分我的經(jīng)驗是不要看技術(shù)指標(biāo)先看痛感。如果團(tuán)隊出現(xiàn)了下面幾種情況才說明單體架構(gòu)真正撐不住了變更頻率嚴(yán)重失衡某幾個模塊每天都在改版本而其他模塊幾個月不動一次。單體的發(fā)布周期被高頻變更模塊綁架低頻模塊為了配合發(fā)布不得不頻繁回歸測試。擴(kuò)容效率低下某模塊存在明顯的資源瓶頸比如大量計算或高內(nèi)存占用但整個應(yīng)用只能整體擴(kuò)容導(dǎo)致資源嚴(yán)重浪費。團(tuán)隊協(xié)作成本激增多人同時在一個代碼倉庫里開發(fā)合并沖突頻繁發(fā)布窗口需要各方協(xié)調(diào)。代碼倉庫的復(fù)雜度已經(jīng)超過了團(tuán)隊溝通的復(fù)雜度。技術(shù)債阻礙演進(jìn)代碼高度耦合改了A模塊必須小心翼翼影響B(tài)模塊團(tuán)隊已經(jīng)無法安全地進(jìn)行重構(gòu)。如果以上四點一條都沒觸到那我真心建議你再冷靜想一段時間。單體架構(gòu)不是恥辱把單體寫好、模塊化邊界清晰配合上完善的CI/CD同樣可以支撐相當(dāng)大的業(yè)務(wù)體量。強(qiáng)行拆分只會把你從一個可控的復(fù)雜系統(tǒng)拖進(jìn)一個更復(fù)雜的分布式系統(tǒng)。2. 四個邊界判斷法業(yè)務(wù)、數(shù)據(jù)、團(tuán)隊、事務(wù)交叉驗證一旦確定要拆下一個問題就是怎么劃邊界。這是拆分最核心、也最容易出錯的地方。很多人上來就按技術(shù)分層拆——Controller拆一個服務(wù)、Service拆一個服務(wù)、DAO拆一個服務(wù)——這屬于典型的自殺式拆分。拆服務(wù)不能看代碼結(jié)構(gòu)要看業(yè)務(wù)結(jié)構(gòu)。我總結(jié)了一套四邊界交叉驗證法把業(yè)務(wù)邊界、數(shù)據(jù)邊界、團(tuán)隊邊界、事務(wù)邊界四個維度放在一起驗證每個候選服務(wù)都要經(jīng)過這四重檢驗全部通過才算合格。2.1 業(yè)務(wù)邊界先別急著寫代碼先把領(lǐng)域模型畫出來微服務(wù)拆分的第一原則是跟著業(yè)務(wù)領(lǐng)域走。這一步需要回到領(lǐng)域驅(qū)動設(shè)計的基本功上雖然這個詞已經(jīng)被講爛了但真正做起來依然非常有效。具體操作上我推薦團(tuán)隊用事件風(fēng)暴Event Storming的方式做一次徹底的領(lǐng)域梳理。找一個會議室準(zhǔn)備好便利貼把系統(tǒng)里所有業(yè)務(wù)事件按時間順序鋪開然后反向追索每個事件是由誰發(fā)起的、需要哪些數(shù)據(jù)、會修改哪些狀態(tài)。這個過程通常需要一到兩天但收益非常大你會發(fā)現(xiàn)很多服務(wù)之間其實根本沒有那么強(qiáng)的業(yè)務(wù)關(guān)聯(lián)其耦合只是歷史代碼造成的假象。做完領(lǐng)域梳理之后識別出聚合根和限界上下文每一個限界上下文就是一個天然的業(yè)務(wù)邊界。注意劃分邊界看的是業(yè)務(wù)能力不是代碼實現(xiàn)層。比如用戶注冊、用戶登錄、用戶資料管理屬于用戶領(lǐng)域的同一個邊界不應(yīng)該因為代碼上分了多個Controller就拆成多個服務(wù)。2.2 數(shù)據(jù)邊界拆分服務(wù)之前先看你的數(shù)據(jù)能不能分離很多團(tuán)隊拆服務(wù)的時候只盯著業(yè)務(wù)功能完全忽視了數(shù)據(jù)歸屬問題。這是大忌。服務(wù)拆分的最終目的是讓每個服務(wù)擁有獨立的生命周期和伸縮能力而這一切的前提是每個服務(wù)必須擁有自己獨占的數(shù)據(jù)存儲。如果一個服務(wù)的所有操作都要跟其他服務(wù)搶同一張數(shù)據(jù)庫表那它就不具備拆分的條件。我在判斷數(shù)據(jù)邊界時有一個非常簡單的標(biāo)準(zhǔn)把候選服務(wù)涉及的數(shù)據(jù)表單獨列出來問自己兩個問題。第一這些表能否脫離其他表獨立存在如果某張表必須通過數(shù)據(jù)庫外鍵關(guān)聯(lián)其他表的字段才能完成業(yè)務(wù)邏輯說明數(shù)據(jù)耦合了。第二如果這個服務(wù)被刪掉這些數(shù)據(jù)還有沒有其他業(yè)務(wù)在依賴如果有說明數(shù)據(jù)歸屬還沒界定清楚。這里我的一個忠告是不要指望通過微服務(wù)架構(gòu)去解決已存在的數(shù)據(jù)庫耦合問題因為大概率解決不了只會讓問題變得更隱蔽。原來單體內(nèi)你還能直接查表定位問題拆成服務(wù)之后數(shù)據(jù)被兩個服務(wù)各管一部分出問題的時候你連SQL都寫不完整排障成本成倍增加。正確的順序是先把數(shù)據(jù)庫的關(guān)系理清楚該拆的表先拆了該做數(shù)據(jù)遷移的先做了然后再考慮代碼層拆服務(wù)。2.3 團(tuán)隊邊界康威定律決定了你的服務(wù)邊界拆服務(wù)本質(zhì)上是在拆組織架構(gòu)因為最終維護(hù)這些服務(wù)的還是人。如果你把某個服務(wù)拆出來卻沒有一個團(tuán)隊哪怕是一個人可以全權(quán)負(fù)責(zé)它那么這個服務(wù)注定會成為公共廁所——人人都能改出了事沒人管??低稍谶@里的作用被很多團(tuán)隊低估了系統(tǒng)的架構(gòu)會鏡像組織的溝通結(jié)構(gòu)。你按什么方式拆分團(tuán)隊你的服務(wù)最終就會變成什么樣。如果你的團(tuán)隊是前端、后端、測試這樣按職能劃分的服務(wù)邊界就算拆得再合理執(zhí)行的時候也會被組織邊界拉回去。所以我的建議是先調(diào)整團(tuán)隊結(jié)構(gòu)再定服務(wù)邊界。每個服務(wù)必須有一個明確的主人也就是一個負(fù)責(zé)它的全生命周期需求、開發(fā)、測試、部署、運(yùn)維的最小團(tuán)隊。這個團(tuán)隊可以只有兩三個人不要貪多。團(tuán)隊和服務(wù)的對應(yīng)關(guān)系最好是單向的——一個團(tuán)隊可以維護(hù)多個內(nèi)聚度高的服務(wù)但一個服務(wù)不能由多個團(tuán)隊共同維護(hù)否則等于沒拆。2.4 事務(wù)邊界強(qiáng)一致與最終一致的分野第四個邊界是事務(wù)邊界這也是拆分中最容易翻車的地方。單體時代一個事務(wù)就能搞定的事情拆成服務(wù)之后跨服務(wù)的數(shù)據(jù)一致性怎么保證這個問題的答案在某種程度上決定了你該不該拆、該怎么拆。在驗證事務(wù)邊界之前先做一道是非題候選服務(wù)之間的數(shù)據(jù)修改是否必須保持強(qiáng)一致如果答案是必須——比如賬戶余額扣減和訂單狀態(tài)更新屬于同一個業(yè)務(wù)操作任何一步失敗都會產(chǎn)生資金差錯——那就要非常謹(jǐn)慎了。我的經(jīng)驗是能保持強(qiáng)一致的事務(wù)操作最好還是留在同一個服務(wù)/同一個數(shù)據(jù)庫內(nèi)不要為了拆而拆。如果業(yè)務(wù)能夠接受最終一致——比如下單后庫存先扣減、異步通知物流或用戶修改手機(jī)號后不影響其他模塊立即生效——那這個服務(wù)就具備了拆分的可行性。但要注意最終一致不等于不做一致性處理你必須為這種跨服務(wù)操作準(zhǔn)備明確的補(bǔ)償機(jī)制和定時對賬。我見過太多團(tuán)隊在拆完之后才發(fā)現(xiàn)很多看似可以最終一致的操作實際上業(yè)務(wù)方要求強(qiáng)一致結(jié)果只能硬著頭皮引入分布式事務(wù)框架性能和復(fù)雜度雙雙爆炸。所以這個驗證一定要在拆分之前做而且要跟業(yè)務(wù)方掰開揉碎了確認(rèn)清楚。3. 拆分粒度與控制面寧可粗一點也不要細(xì)到失控3.1 粒度選擇的核心參考維度變更頻率、數(shù)據(jù)規(guī)模、團(tuán)隊規(guī)模很多人在規(guī)劃微服務(wù)拆分時第一個問題就是服務(wù)拆多大合適。這其實沒有標(biāo)準(zhǔn)答案但可以依據(jù)三個維度來輔助決策。第一個維度是變更頻率。一個模塊如果代碼量很大但一年到頭只改幾次那它就沒有拆出來的必要留在單體里反而穩(wěn)定。真正值得拆分的是那些變更頻繁、每星期都在發(fā)版的模塊。拆出來的目的就是為了讓這個高頻變更模塊能夠獨立上線而不拖累其他低頻模塊。第二個維度是數(shù)據(jù)規(guī)模。如果一個業(yè)務(wù)模塊的數(shù)據(jù)量已經(jīng)大到了需要單獨分庫的水平那它就應(yīng)該拆出去。反過來如果幾張表加起來才幾十萬行放在一起完全沒有性能壓力那拆分就是純增加復(fù)雜度。第三個維度是團(tuán)隊規(guī)模。我常說一句話團(tuán)隊的溝通帶寬決定了服務(wù)粒度的下限。一個只有五人左右的技術(shù)團(tuán)隊私有微服務(wù)最好控制在八到十個以內(nèi)再往上拆光是服務(wù)之間的聯(lián)調(diào)對接就會消耗大量溝通成本最終反而拖慢交付速度。我的建議是初期拆分時寧可粗一點把幾個強(qiáng)相關(guān)的業(yè)務(wù)合并成一個服務(wù)也不要為了追求微而拆到極致。粒度太粗的壞處是后續(xù)可以再做二次拆分但粒度太細(xì)的壞處是治理成本會立刻吞噬你的開發(fā)效率而且極難回頭。3.2 演進(jìn)式拆分先拆骨架服務(wù)再逐步細(xì)化拆分不需要一步到位完全可以采用漸進(jìn)式的策略。我實操下來比較穩(wěn)妥的路徑是從外向內(nèi)、從穩(wěn)到變。第一輪只拆出三個骨架服務(wù)注冊登錄服務(wù)、基礎(chǔ)數(shù)據(jù)服務(wù)比如用戶、組織、字典、核心業(yè)務(wù)服務(wù)。這三個服務(wù)的特點是不依賴太多其他模塊拆出去之后系統(tǒng)的運(yùn)行穩(wěn)定性受影響最小。先跑通SpringCloud全家桶的基礎(chǔ)設(shè)施注冊中心、網(wǎng)關(guān)、配置中心、鏈路追蹤驗證這套體系在你的業(yè)務(wù)場景下是否真的能帶來收益。第二輪再根據(jù)運(yùn)行情況決定要不要繼續(xù)拆。如果核心業(yè)務(wù)服務(wù)內(nèi)部仍然存在變更頻率差異很大的模塊再考慮從內(nèi)部拆出獨立的服務(wù)。這時候你的基礎(chǔ)設(shè)施已經(jīng)經(jīng)過驗證拆分的風(fēng)險就小很多了。這種演進(jìn)式拆分的好處是每一步都是可回滾的。如果你拆完第一輪發(fā)現(xiàn)微服務(wù)帶來的收益遠(yuǎn)低于預(yù)期回到單體只需要合并幾個倉庫成本完全可控。但如果一開始就一次性拆十幾個服務(wù)想回滾就沒那么容易了光是數(shù)據(jù)遷移就夠你喝一壺。3.3 三種典型拆分范式的適用場景對比在具體操作層面服務(wù)拆分的范式大體有三種按不同的業(yè)務(wù)和團(tuán)隊情況選擇。第一種是縱向拆分按業(yè)務(wù)模塊垂直切分。這是最常用的方式比如把訂單、用戶、商品、支付分別拆成獨立服務(wù)。每個服務(wù)自治程度最高業(yè)務(wù)邊界清晰適合團(tuán)隊結(jié)構(gòu)也按業(yè)務(wù)線劃分的組織。第二種是橫向拆分按功能層次切分。比如把公共的基礎(chǔ)能力文件上傳、短信發(fā)送、消息通知抽成獨立的平臺服務(wù)業(yè)務(wù)中臺服務(wù)再聚合這些能力向下游提供統(tǒng)一接口。這種方式適合服務(wù)復(fù)用需求強(qiáng)的場景但要注意控制層級深度避免出現(xiàn)業(yè)務(wù)服務(wù)依賴中臺服務(wù)、中臺服務(wù)又依賴基礎(chǔ)服務(wù)、基礎(chǔ)服務(wù)還依賴業(yè)務(wù)服務(wù)的循環(huán)依賴。第三種是混合拆分業(yè)務(wù)服務(wù)按縱向拆公共服務(wù)按橫向抽。這是大多數(shù)中大型系統(tǒng)的實際選擇也是我比較推薦的形態(tài)。先用縱向拆分定義業(yè)務(wù)邊界保證核心業(yè)務(wù)自治再把公共能力抽取出來做收斂防止重復(fù)建設(shè)。思路清晰后還需要結(jié)合自己團(tuán)隊的交付節(jié)奏來做平衡。比如你所在的團(tuán)隊對快速上線要求很高那就要警惕公共服務(wù)帶來的額外協(xié)調(diào)成本如果團(tuán)隊對強(qiáng)復(fù)用要求很高縱向拆分時也要提前預(yù)留公共能力的接口。4. 拆完之后SpringCloud的落地細(xì)節(jié)注冊、配置、網(wǎng)關(guān)、調(diào)用鏈服務(wù)邊界劃分清楚以后才輪到SpringCloud技術(shù)棧的落地。這一部分如果只停留在會用層面的配置后面運(yùn)行期出問題時會很難排查。所以我結(jié)合幾次實際項目經(jīng)驗把每一步里最關(guān)鍵的注意點整理出來。4.1 注冊中心選型Nacos和Eureka怎么選選注冊中心的本質(zhì)是在選 服務(wù)發(fā)現(xiàn)機(jī)制 和 配置管理 的組合。國內(nèi)現(xiàn)在的環(huán)境下Nacos的使用頻率和生態(tài)完整度都遠(yuǎn)高于Eureka而且Eureka在開源維護(hù)層面已經(jīng)進(jìn)入停滯狀態(tài)不少團(tuán)隊還在用舊版本跑生產(chǎn)遇到問題基本得靠社區(qū)經(jīng)驗去填。如果是從零開始我建議直接用Nacos。Nacos同時提供服務(wù)注冊發(fā)現(xiàn)和配置管理兩大能力這意味著你可以少部署一套配置中心組件。細(xì)節(jié)上服務(wù)實例注冊使用臨時節(jié)點還是持久節(jié)點直接決定你的服務(wù)在宕機(jī)后能否被快速摘除。我推薦使用臨時節(jié)點心跳超時時間按默認(rèn)值走就夠了不要把心跳超時調(diào)得過大否則故障時服務(wù)提供者依然會留在注冊列表里導(dǎo)致調(diào)用方請求大面積超時。部署Nacos時我習(xí)慣用至少三個節(jié)點組成集群并啟用持久化存儲。配置中心的namespace按環(huán)境隔離比如dev、test、prod各一套這樣可以避免多環(huán)境之間互相干擾。這里有一個常見誤區(qū)很多人把配置都放在默認(rèn)namespace里然后環(huán)境切換的時候忘了刷新導(dǎo)致開發(fā)環(huán)境把測試環(huán)境的配置加載進(jìn)來。這種問題在微服務(wù)架構(gòu)里特別難排查因為服務(wù)本身沒報錯只是在錯誤的配置下運(yùn)行。4.2 網(wǎng)關(guān)設(shè)計入口聚合與業(yè)務(wù)收斂服務(wù)拆完之后所有外部請求都需要一個統(tǒng)一入口這就是網(wǎng)關(guān)的職責(zé)。我使用Spring Cloud Gateway的經(jīng)驗是網(wǎng)關(guān)只做路由轉(zhuǎn)發(fā)、跨域處理、統(tǒng)一鑒權(quán)校驗、限流熔斷這四件事絕對不寫任何業(yè)務(wù)邏輯。一個非常容易踩的坑是有人喜歡把多個內(nèi)部服務(wù)的請求邏輯寫在網(wǎng)關(guān)里做聚合搞出一個大網(wǎng)關(guān)。表面上看起來前端調(diào)用方便了但網(wǎng)關(guān)代碼越來越重最后變成一個新的單體不改不行改了又容易全站故障。正確做法是如果確實需要前后端接口的聚合不要讓網(wǎng)關(guān)來做而是單獨設(shè)計一個BFFBackend for Frontend層服務(wù)這個服務(wù)可以專門為前端聚合接口然后調(diào)用各個基礎(chǔ)服務(wù)。網(wǎng)關(guān)只負(fù)責(zé)對外部流量做第一層收斂BFF做業(yè)務(wù)聚合基礎(chǔ)服務(wù)做領(lǐng)域能力三層職責(zé)分離后續(xù)維護(hù)會清爽很多。網(wǎng)關(guān)還有一個容易被忽略的配置項路由的超時時間。默認(rèn)的響應(yīng)超時往往不夠用如果某個下游服務(wù)處理大文件或復(fù)雜查詢耗時較長而網(wǎng)關(guān)超時設(shè)置得太短就會出現(xiàn)服務(wù)實際處理成功但客戶端收到超時的假失敗。這種問題最容易出現(xiàn)在支付回調(diào)、文件導(dǎo)出這類異步或慢操作場景中。4.3 配置中心與多環(huán)境管理改配置時要能快速回滾服務(wù)多起來之后配置管理就變成了一個高危操作。我見過不少微服務(wù)故障根因都是某個人改了配置中心的某個值影響面波及全部服務(wù)而且因為服務(wù)間有緩存故障還帶延遲性特別難追。所以配置的管理流程要遠(yuǎn)比單體時代更嚴(yán)謹(jǐn)。我的做法是所有涉及生產(chǎn)環(huán)境的配置變更必須走配置中心的發(fā)布審核流程改動前先看diff發(fā)布后快速驗證下游。同時要確保配置中心的自動保存歷史版本可用這樣一旦出現(xiàn)問題能一鍵回滾到上一版本。另外命名空間和服務(wù)分組要設(shè)計得足夠清晰。比如每個服務(wù)一個命名空間公共配置單獨放一個共享空間這樣各服務(wù)引用的時候不會亂。實際操作中我還發(fā)現(xiàn)配置文件的格式盡量統(tǒng)一不要一個服務(wù)用properties、一個服務(wù)用yaml排障的時候格式差異會浪費很多時間。4.4 分布式事務(wù)與最終一致性的落地選擇前面在事務(wù)邊界部分我們已經(jīng)確定了哪些業(yè)務(wù)能接受最終一致。剩下那些跨服務(wù)但又要求一致性的操作怎么辦我的方案是按業(yè)務(wù)等級分治而不是一刀切全部引入強(qiáng)一致框架。對于嚴(yán)格強(qiáng)一致的操作我目前用得比較順的是AT模式事務(wù)框架。但要注意AT模式依賴全局鎖會產(chǎn)生一定的性能開銷所以僅限并發(fā)量不高的場景。如果并發(fā)量大但業(yè)務(wù)又強(qiáng)一致那比較好的辦法不是用分布式事務(wù)框架硬扛而是重構(gòu)業(yè)務(wù)設(shè)計看能不能把操作放到同一個服務(wù)里完成。對于可以最終一致的場景我推薦用本地消息表加消息隊列的方式。業(yè)務(wù)流程中先寫入本地事務(wù)表再異步發(fā)送消息消費者消費成功后回調(diào)失敗則定時重試。整套機(jī)制配合定時對賬能夠做到大部分一致性需求。這里我不建議為了炫技而把所有用到分布式事務(wù)的場景都換成TCC模式。TCC的Confirm和Cancel邏輯寫起來費時費力業(yè)務(wù)稍復(fù)雜一點幾乎沒有團(tuán)隊能真正把每個分支都調(diào)對。能用自己的框架解決的事情就不要平白增加工作量。5. 拆分后的治理成本沒有配套的監(jiān)控就別急著拆5.1 可觀測性的三支柱日志、指標(biāo)、鏈路追蹤缺一不可單體時代排查問題看一份日志、查一個數(shù)據(jù)庫就差不多了。拆成微服務(wù)之后一次用戶請求可能要穿透六個服務(wù)每個服務(wù)都有自己的日志文件沒有一套統(tǒng)一的可觀測性體系排障只能靠猜??捎^測性包含三塊日志、指標(biāo)和鏈路追蹤。日志解決具體發(fā)生了什么指標(biāo)解決系統(tǒng)狀態(tài)是否健康鏈路追蹤解決一次請求在多個服務(wù)之間是如何流轉(zhuǎn)的。三塊缺一不可。我見過有些團(tuán)隊只做了日志收集沒做鏈路追蹤。這樣當(dāng)一個請求超時你看到的是一堆來自不同服務(wù)的日志碎片根本拼不出一個完整的請求路徑。這時候你只能逐個服務(wù)去翻日志效率極低。而鏈路追蹤通過一個traceId就能把整個調(diào)用鏈串起來哪個環(huán)節(jié)慢、哪個環(huán)節(jié)報錯一目了然。5.2 日志按服務(wù)聚合后再按traceId串聯(lián)檢索微服務(wù)的日志處理我的建議是分兩個階段做。第一階段先把所有服務(wù)的日志統(tǒng)一采集到日志平臺里按服務(wù)名和日志級別建立索引第二階段接入鏈路追蹤組件讓每個請求都生成一個traceId并且讓這個traceId在HTTP調(diào)用時自動傳遞到下游服務(wù)。實際操作中鏈路追蹤的透傳需要依賴日志里也打印traceId。如果日志系統(tǒng)的索引沒建好每個服務(wù)的traceId格式不一致那日志就依然是一盤散沙。曾經(jīng)有段時間我們的服務(wù)日志里traceId就是空的因為日志框架的配置里沒有把MDC里的traceId帶出來排查問題時只能通過時間大致對齊特別痛苦。后來我改了日志格式在pattern里加上traceId字段再按這個字段做索引聚合排查體驗才迎來真正的拐點。5.3 穩(wěn)定性治理熔斷、限流、降級不能停留在紙面上服務(wù)拆完之后一個有問題的下游服務(wù)如果處理能力崩了會發(fā)生什么如果沒有任何保護(hù)機(jī)制上游服務(wù)會一直等待直到超時然后線程資源被耗盡最終引起服務(wù)雪崩。所以穩(wěn)定性治理必須在基礎(chǔ)設(shè)施層配置好。熔斷和降級我這里直接說經(jīng)驗首次接入時不要糾結(jié)該哪個架構(gòu)果斷選擇成熟的降級限流組件靠配置解決大多數(shù)問題。如果團(tuán)隊里有精力去鉆研自定義策略再逐步加規(guī)則。接口的閾值先按經(jīng)驗值粗配比如RT超過500毫秒或異常比例達(dá)到20%就觸發(fā)熔斷運(yùn)行兩周之后根據(jù)線上實際情況再調(diào)整。降級策略要提前設(shè)計好返回值。比如庫存服務(wù)掛了下單服務(wù)要怎么降級是提示庫存繁忙還是返回一個兜底庫存值這兩種策略的取舍要跟業(yè)務(wù)方提前對齊別等故障發(fā)生了才討論。6. 容易翻車的拆分陷阱我把真實經(jīng)驗都寫在這里6.1 過度拆分的三個典型癥狀如果你們的服務(wù)數(shù)量已經(jīng)開始失控可以用下面三個特征自查。第一調(diào)用鏈路過長。下單流程如果經(jīng)歷了三層以上的服務(wù)跳轉(zhuǎn)而每一跳都有網(wǎng)絡(luò)開銷和序列化開銷那性能一定有問題。我見過有的系統(tǒng)下單流程要走七跳響應(yīng)時間輕松超過兩秒用戶差評如潮。第二一個簡單的需求改動需要同時修改多個服務(wù)。這說明服務(wù)邊界沒有真正切對業(yè)務(wù)本身在一個服務(wù)內(nèi)才能內(nèi)聚你卻把它強(qiáng)行拆開了。這種拆分的最終結(jié)果必然是邏輯重復(fù)和不停聯(lián)調(diào)。第三服務(wù)之間的依賴關(guān)系已經(jīng)無人能說清。如果團(tuán)隊里已經(jīng)沒有人能畫出一張完整的服務(wù)依賴圖那你們的微服務(wù)架構(gòu)已經(jīng)失控了。失控的架構(gòu)比單體更糟糕因為連安全的回滾路徑都沒有。6.2 分布式事務(wù)的濫用一個域的教訓(xùn)我之前參與過一個項目團(tuán)隊為了讓系統(tǒng)架構(gòu)先進(jìn)把原本一個數(shù)據(jù)庫的事務(wù)操作全部拆到了多個服務(wù)里然后引入分布式事務(wù)框架強(qiáng)頂一致性。一開始功能上沒有問題但到了大促期間性能立刻暴露出來全局鎖導(dǎo)致并發(fā)吞吐量劇降大量事務(wù)處理超時最終不得不緊急回滾到合并部署。這個教訓(xùn)讓我深刻理解到微服務(wù)的拆分不能以犧牲數(shù)據(jù)一致性為代價。如果強(qiáng)一致是硬要求最好的辦法就是不要拆分如果必須拆分就要盡早設(shè)計好異步補(bǔ)償和最終一致的方案。6.3 無效的微治理成本大于收益時的及時止損拆錯了怎么辦及時止損比硬撐更重要。我發(fā)現(xiàn)不少團(tuán)隊在拆完之后發(fā)現(xiàn)復(fù)雜度上升、交付變慢但礙于已經(jīng)拆了半年投入太大的心態(tài)不愿意再走回頭路。這種心態(tài)在技術(shù)上是大忌。如果拆完的服務(wù)沒有明確的獨立發(fā)展空間如果它的存在只是讓系統(tǒng)復(fù)雜度單調(diào)增加那合并回去就是最優(yōu)解。我在組織合并服務(wù)時一般遵循把強(qiáng)相關(guān)的服務(wù)合并為一個保留每個服務(wù)獨立配置和部署的能力同時保證合并后仍然可以通過擴(kuò)展實現(xiàn)橫向擴(kuò)容。反正是退回到一個比原單體更清晰的中間態(tài)成本可控收益卻很明確。6.4 拆分是組織問題不是純技術(shù)問題走到最后你會發(fā)現(xiàn)微服務(wù)拆分真正難的地方從來不是技術(shù)而是組織和協(xié)作。Service拆出來團(tuán)隊沒有對應(yīng)的負(fù)責(zé)制服務(wù)就會自然回歸到人人可改、無人負(fù)責(zé)的公共狀態(tài)。如果組織還是按前端、后端、測試這種職能劃分微服務(wù)的獨立部署優(yōu)勢就會被部門墻徹底消解。所以在啟動拆分之前我建議你先跟團(tuán)隊把關(guān)鍵事項對清楚每個服務(wù)的Owner是誰服務(wù)間的接口變更通過什么流程達(dá)成一致線上故障由哪個角色來做第一響應(yīng)這些問題的答案決定了微服務(wù)的架構(gòu)能走多遠(yuǎn)。我個人最深的體會是如果你能用單體架構(gòu)解決的問題就不要輕易上微服務(wù)如果決定要上就把上面這幾個問題想透了再動手。SpringCloud只是工具把服務(wù)拆好、治理好靠的是對整個系統(tǒng)邊界的持續(xù)思考和務(wù)實取舍。