設(shè)計模式實戰(zhàn)指南:10個常用模式與避坑清單)
做微服務(wù)這些年我最大的感受是拆服務(wù)從來不是項目的終點反而是麻煩的起點。單體應(yīng)用里一次方法調(diào)用就能搞定的事拆成微服務(wù)之后要面對網(wǎng)絡(luò)超時、數(shù)據(jù)一致性、動態(tài)擴縮容、配置同步、故障隔離這些單體時代幾乎不用操心的問題。所謂微服務(wù)設(shè)計模式說白了就是這套新問題上被反復(fù)驗證過的標準解法。這篇文章我把實際項目里最常用的10個模式挨個拆開講重點講清楚每個模式解決什么問題、落地時要注意哪些細節(jié)結(jié)尾附上速查表和避坑清單建議先收藏遇到具體場景再回來對照。1. 為什么微服務(wù)架構(gòu)必須聊設(shè)計模式1.1 微服務(wù)不是“拆完就完事”很多人對微服務(wù)的理解停留在“把一個龐大的工程拆成多個服務(wù)各自獨立部署、獨立擴展”這一步。這個理解沒錯但真的只算第一步。服務(wù)一旦拆開原本在一個進程里完成的事情現(xiàn)在變成了跨進程、跨網(wǎng)絡(luò)的協(xié)作出問題的點也成倍增加。舉一個很常見的場景單體應(yīng)用里用戶下單這個動作可能只是在一個進程里調(diào)用幾個方法數(shù)據(jù)庫事務(wù)可以保證扣庫存、減余額、生成訂單要么全部成功要么全部回滾。但拆成微服務(wù)以后訂單服務(wù)和庫存服務(wù)各自獨立部署一次下單請求要先調(diào)用訂單服務(wù)再調(diào)用庫存服務(wù)中間任何一個調(diào)用失敗或者網(wǎng)絡(luò)超時整個數(shù)據(jù)就可能對不上。類似的難題還有很多服務(wù)實例的地址會動態(tài)變化客戶端怎么始終找到正確的目標中間件地址改了難道要逐個重啟幾十個服務(wù)實例某個依賴服務(wù)變慢了怎么避免它拖垮整條調(diào)用鏈這些問題沒有一個能在單體時代直接找到答案。設(shè)計模式就是針對這類反復(fù)出現(xiàn)的協(xié)作問題給出經(jīng)過驗證的結(jié)構(gòu)化方案。1.2 三類核心問題四個解決維度我把微服務(wù)架構(gòu)下反復(fù)出現(xiàn)的問題歸納成三類服務(wù)之間怎么通信、怎么被找到這屬于位置透明問題數(shù)據(jù)在多個服務(wù)間怎么保持一致這屬于一致性問題故障怎么隔離、怎么恢復(fù)這屬于韌性容錯問題。再加上運維層面的配置管理基本就構(gòu)成了設(shè)計模式的主要覆蓋范圍。把這些維度想清楚再回頭看各種模式就不會覺得它們是零散的知識點。所謂的10個常用模式其實是圍繞這三類問題衍生出來的不同解法。理解了問題本身模式才真正有記憶點。1.3 10個模式的整體分類我按自己的習(xí)慣把這10個模式分成四組先搭一個整體框架后面逐個展開分組包含模式核心解決什么接入與位置透明API網(wǎng)關(guān)/聚合器、服務(wù)注冊與發(fā)現(xiàn)、配置中心客戶端怎么連、服務(wù)怎么被找到、配置怎么統(tǒng)一管容錯與自愈斷路器、限流與背壓、冪等與重試故障怎么隔離、流量怎么控制、重復(fù)請求怎么兜底數(shù)據(jù)與一致性獨立數(shù)據(jù)庫、Saga、CQRS數(shù)據(jù)歸屬、跨服務(wù)事務(wù)、讀寫壓力分離異步與解耦事件驅(qū)動模式服務(wù)間解耦、異步削峰、系統(tǒng)伸縮這個分類不是教科書標準是我自己在項目里應(yīng)用時的經(jīng)驗劃分。好處是遇到問題時能快速定位該翻哪個模式的答案。2. 接入與位置透明服務(wù)如何被找到2.1 聚合器/API網(wǎng)關(guān)模式統(tǒng)一入口別裸奔在完全不設(shè)網(wǎng)關(guān)的微服務(wù)架構(gòu)里客戶端要直接面對十幾個服務(wù)的幾十個實例地址端口、協(xié)議、路徑都不一樣。這種“裸奔”狀態(tài)會帶出三個問題客戶端要維護大量地址服務(wù)擴縮容時地址一變客戶端就跟著掛每個服務(wù)都要自己實現(xiàn)鑒權(quán)、限流、日志重復(fù)勞動還容易漏遇到一個頁面需要聚合多個服務(wù)數(shù)據(jù)的情況客戶端要并發(fā)去調(diào)多個接口再手動組裝網(wǎng)絡(luò)開銷大邏輯還不好維護。聚合器模式的核心思想是把這些問題收口到一層。客戶端只跟一個或少數(shù)幾個入口打交道由網(wǎng)關(guān)層負責路由、鑒權(quán)、限流、協(xié)議轉(zhuǎn)換必要的時候把多個底層服務(wù)的響應(yīng)聚合起來一次性返回。聚合器這個名字起得很形象——它把多個小結(jié)果“聚合”成一個完整響應(yīng)。我在落地聚合器時最看重三個能力。第一是路由按路徑前綴或Header把請求轉(zhuǎn)發(fā)到對應(yīng)服務(wù)第二是聚合網(wǎng)關(guān)層并發(fā)調(diào)用多個服務(wù)再合并響應(yīng)比如詳情頁需要用戶信息加訂單信息客戶端只調(diào)一次網(wǎng)關(guān)接口就夠了第三是橫切關(guān)注點統(tǒng)一鑒權(quán)、統(tǒng)一日志、統(tǒng)一異常包裝這些邏輯放在每個業(yè)務(wù)服務(wù)里很啰嗦放網(wǎng)關(guān)層一次搞定。這里有一條很重要的邊界網(wǎng)關(guān)不能變成新的巨無霸。我見過有項目把業(yè)務(wù)聚合邏輯全塞進網(wǎng)關(guān)最后網(wǎng)關(guān)自己成了沒人敢動的大泥球。正確的做法是網(wǎng)關(guān)只做輕量路由和通用橫切邏輯復(fù)雜的業(yè)務(wù)聚合放到BFF層或者專門的聚合服務(wù)里讓每層保持單一職責。2.2 服務(wù)注冊與發(fā)現(xiàn)模式動態(tài)感知服務(wù)變化微服務(wù)實例是動態(tài)的擴容、縮容、故障重啟每時每刻都在發(fā)生。如果客戶端還用寫死的IP去調(diào)用擴容出來的新實例永遠接不到流量掛掉的實例還會被持續(xù)調(diào)用直到超時才被發(fā)現(xiàn)。服務(wù)注冊與發(fā)現(xiàn)模式解決的就是這個問題。它的機制不復(fù)雜所有服務(wù)啟動時把自己的地址注冊到注冊中心運行期間持續(xù)發(fā)送心跳?;羁蛻舳苏{(diào)用某個服務(wù)時并不直接指定地址而是先從注冊中心拿到一份健康的實例列表再按負載均衡策略選中其中一個發(fā)起調(diào)用。常見的注冊中心有Consul、Nacos這類產(chǎn)品它們之間的差異主要體現(xiàn)在一致性模型和健康檢查機制上。這個模式落地時有兩個容易被忽視的坑。第一個是“發(fā)現(xiàn)延遲”新注冊的實例不會立刻被所有客戶端感知所以發(fā)布上線時最好預(yù)留一段預(yù)熱期別一注冊就把流量猛打過去否則容易把剛啟動還在加載緩存的實例壓垮。第二個坑是注冊中心自身的可用性很多注冊中心都設(shè)計了自我保護機制寧可短暫返回舊的實例列表也不要因為注冊中心抖動讓全鏈路癱瘓。我早期把注冊中心當成普通數(shù)據(jù)庫來調(diào)把超時時間設(shè)得特別短結(jié)果注冊中心一抖動整個集群跟著抖后來才明白“盡力可用”要優(yōu)先于“絕對一致”。2.3 配置中心模式配置與代碼解耦配置中心在微服務(wù)體系里的地位經(jīng)常被低估。有人覺得它無非是把配置文件搬到遠程存儲為什么要單獨作為一個設(shè)計模式來講因為配置變更在微服務(wù)里的代價遠高于單體架構(gòu)。單體應(yīng)用改配置重啟一次就完事微服務(wù)可能幾十個實例要改一個數(shù)據(jù)庫連接地址逐個重啟既慢又容易出錯。配置中心的核心價值有三點集中管理、動態(tài)發(fā)布、版本可追溯。集中管理是指所有服務(wù)的配置統(tǒng)一存放方便查看和審計動態(tài)發(fā)布是指配置變更后能實時推送到各服務(wù)實例不用重啟版本可追溯是指每次變更都有記錄出問題能快速回滾到上一個穩(wěn)定版本。我選型和落地配置中心時踩過幾次坑總結(jié)出兩個心得。第一配置必須區(qū)分“啟動時只讀”和“運行時可變”兩類。像數(shù)據(jù)庫連接池大小、限流閾值這類參數(shù)可以運行時調(diào)整服務(wù)端口、網(wǎng)絡(luò)超時這類參數(shù)就不能熱更新設(shè)計配置分組時要按這個維度劃分否則會誤以為所有配置都能動態(tài)變更。第二配置中心本身掛了服務(wù)不能跟著下線。本地必須保留配置緩存的兜底方案啟動時如果遠程配置拉不到就使用本地上次生效的配置并告警讓服務(wù)先活著而不是直接啟動失敗。3. 容錯與自愈系統(tǒng)如何扛住故障3.1 斷路器模式失敗快返回別傻等微服務(wù)拆細之后一個請求往往會經(jīng)過多個服務(wù)。只要中間某一個服務(wù)變慢或者不可用依賴它的請求就會一直阻塞等待線程資源被越占越多最終可能導(dǎo)致整條調(diào)用鏈的線程池耗盡這就是典型的故障雪崩。斷路器模式的思路和家里電路里的空氣開關(guān)如出一轍當連續(xù)失敗次數(shù)達到閾值就主動“跳閘”后續(xù)請求快速失敗返回不再實際調(diào)用下游服務(wù)經(jīng)過一段冷卻時間后放少量請求過去試探如果成功就慢慢合閘恢復(fù)如果失敗就繼續(xù)保持打開狀態(tài)。斷路器有三個關(guān)鍵狀態(tài)關(guān)閉、打開、半開。關(guān)閉狀態(tài)下請求正常轉(zhuǎn)發(fā)失敗率達到閾值就切換到打開打開狀態(tài)直接返回降級結(jié)果或者快速拋異常半開狀態(tài)放少量流量試探依據(jù)成功率決定是回到關(guān)閉還是重新打開。我給關(guān)鍵鏈路配斷路器時有一個重要感受參數(shù)不能照抄模板。熔斷閾值、超時時間、冷卻時間都要結(jié)合接口真實的SLO來定。比如一個接口正常P99是200毫秒超時時間可以設(shè)在300到500毫秒之間如果接口平時失敗率很低閾值可以設(shè)在5%到10%。還要注意一點超時時間別設(shè)太長否則即使熔斷了請求在等待超時的過程中依然會積壓線程資源熔斷效果會大打折扣。另外我發(fā)現(xiàn)很多人對斷路器有一個誤解以為它只對拋異常和超時生效。其實“慢而不掛”的接口同樣會拖垮系統(tǒng)所以實現(xiàn)時一定要把慢調(diào)用也計入失敗次數(shù)否則一個性能逐漸變差的接口還是會在不知不覺中把依賴方全部拖死。3.2 限流與背壓模式流量大時先保命限流是保護系統(tǒng)免于過載的最后一道防線。它的核心邏輯是控制進入系統(tǒng)的請求速率超過閾值的請求要么排隊、要么拒絕、要么降級響應(yīng)。限流和斷路器定位不同斷路器是下游已經(jīng)出問題才觸發(fā)保護限流是上游流量本身就超出系統(tǒng)處理能力時的主動防御。常見的限流算法有固定窗口、滑動窗口、令牌桶、漏桶四種。固定窗口實現(xiàn)最簡單但窗口邊界容易出現(xiàn)雙倍突發(fā)流量滑動窗口對突發(fā)更平滑令牌桶允許一定量的突發(fā)流量適合大部分業(yè)務(wù)接口漏桶則強制平滑輸出速率適合下游能力特別固定的場景。選型時不用盲目追求復(fù)雜大部分業(yè)務(wù)用令牌桶就夠真正需要嚴格削峰平滑的再考慮漏桶思路。限流的落地位置也很講究。我一般的做法是網(wǎng)關(guān)層做全局粗粒度的限流比如整個系統(tǒng)入口QPS上限核心服務(wù)內(nèi)部再做細粒度的接口級、用戶級限流。比如系統(tǒng)總?cè)肟谌萘渴?萬QPS但某個下單接口的真實瓶頸可能只有2000這種粒度差異只有放到服務(wù)內(nèi)部才能精細化控制單靠網(wǎng)關(guān)一刀切很容易誤傷。背壓這個概念經(jīng)常和限流一起出現(xiàn)。它原本是反應(yīng)式編程里的術(shù)語意思是當下游處理不過來時把壓力反向傳導(dǎo)給上游讓上游主動降低流速。落到實際場景里最典型的就是消息隊列消費者處理速度跟不上生產(chǎn)速度時與其讓消息無限積壓不如監(jiān)控積壓量觸發(fā)消費者擴容或者丟棄非關(guān)鍵消息。很多團隊只配了入口限流卻沒管消息積壓最后消息越堆越多重啟后補消費又引發(fā)第二輪高峰這種連鎖反應(yīng)在小團隊里尤其常見。3.3 冪等與重試模式重復(fù)請求也要穩(wěn)網(wǎng)絡(luò)請求在分布式環(huán)境里一定會發(fā)生重試??蛻舳顺瑫r會重發(fā)網(wǎng)關(guān)的重試機制會重發(fā)消息隊列的消費確認也可能導(dǎo)致同一條消息被處理兩次。如果服務(wù)端不做冪等處理一個訂單可能被創(chuàng)建兩次、一筆扣款可能被扣兩次。冪等設(shè)計模式解決的就是“同一個請求執(zhí)行多次和執(zhí)行一次效果完全相同”這個問題。簡單可靠的落地方式是用冪等表每次請求攜帶一個全局唯一的業(yè)務(wù)ID服務(wù)端先查冪等表如果存在就直接返回第一次處理的結(jié)果不存在就插入記錄并執(zhí)行真正的業(yè)務(wù)邏輯。這里的關(guān)鍵在“插入”環(huán)節(jié)一定要給業(yè)務(wù)ID建立唯一索引依靠數(shù)據(jù)庫約束來攔截并發(fā)場景下的重復(fù)插入。我在這上面踩過很深的坑。當時只做了Redis里的冪等校驗沒有加數(shù)據(jù)庫唯一約束結(jié)果兩個并發(fā)請求同時通過了Redis校驗雙雙走到數(shù)據(jù)庫插入重復(fù)數(shù)據(jù)照樣進了庫。后來改成Redis預(yù)校驗加數(shù)據(jù)庫唯一索引雙重保障問題才徹底堵住。所以我的經(jīng)驗是Redis可以做流量入口的快速攔截但最終兜底必須落在數(shù)據(jù)庫約束上這是唯一不會因為緩存過期而失效的防線。重試同樣有講究不是所有請求都適合自動重試。寫操作一定要在有冪等保障的前提下才能重試否則重試就是放大錯誤的放大器。另外重試必須配合退避策略比如指數(shù)退避加隨機抖動否則容易出現(xiàn)重試風暴。我見過一個真實事故調(diào)用方對超時請求1秒后重試下游服務(wù)本身已經(jīng)變慢結(jié)果所有調(diào)用方幾乎同時發(fā)起重試下游直接被第二輪流量壓垮最后不得不緊急上熔斷才穩(wěn)住局面。4. 數(shù)據(jù)與一致性狀態(tài)如何不飄4.1 獨立數(shù)據(jù)庫模式服務(wù)自治的底線微服務(wù)架構(gòu)里有一條基本原則每個服務(wù)擁有自己的數(shù)據(jù)存儲其他服務(wù)不能直接訪問自己的表。這個模式通常叫“Database per Service”中文常譯為獨立數(shù)據(jù)庫模式。它保證了服務(wù)之間的數(shù)據(jù)邊界讓每個服務(wù)可以獨立選型存儲、獨立擴展表結(jié)構(gòu)的變化也只會影響自己不會拉著別的服務(wù)一起改。獨立數(shù)據(jù)庫模式帶來的最大收益是解耦。如果兩個服務(wù)共享同一個庫改表結(jié)構(gòu)就要多方協(xié)調(diào)慢查詢也會互相干擾一個服務(wù)的大查詢就可能把另一個服務(wù)的數(shù)據(jù)庫資源拖垮。各自獨立之后這些風險從物理層面就被隔離了。但這個模式也有明顯的代價。最大的代價是跨服務(wù)的復(fù)雜查詢變難原來一條SQL JOIN就能完成的事情現(xiàn)在要從多個服務(wù)分別取數(shù)再在內(nèi)存里拼裝結(jié)果。這也是為什么很多團隊在實際操作中對一些強關(guān)聯(lián)的數(shù)據(jù)仍然選擇同庫分Schema的方式臨時過渡。我的個人觀點是核心業(yè)務(wù)域盡量做到獨立庫邊界上特別緊密的關(guān)聯(lián)關(guān)系可以先同庫分表等業(yè)務(wù)穩(wěn)定后再逐步拆出去。架構(gòu)演進本來就是分階段推進的一步到位反而容易出大問題。4.2 Saga分布式事務(wù)放棄強一致?lián)肀ё罱K一致分布式環(huán)境下沒有真正意義上的全局事務(wù)傳統(tǒng)數(shù)據(jù)庫的ACID屬性無法跨服務(wù)保證。Saga模式是分布式事務(wù)領(lǐng)域最務(wù)實的解決方案它的核心思想是把一個跨服務(wù)的大事務(wù)拆成一系列本地事務(wù)每個本地事務(wù)都配有對應(yīng)的補償操作。當鏈路中某一環(huán)失敗時就把前面已經(jīng)執(zhí)行成功的本地事務(wù)逐一執(zhí)行補償把數(shù)據(jù)恢復(fù)到初始狀態(tài)。Saga的實現(xiàn)方式主要分兩類編排式和協(xié)同式。編排式有一個中心協(xié)調(diào)者它負責告訴各個服務(wù)接下來該執(zhí)行什么操作失敗時按順序觸發(fā)補償協(xié)同式?jīng)]有中心協(xié)調(diào)者靠事件在各服務(wù)間自發(fā)傳遞完成業(yè)務(wù)串聯(lián)。兩種方式各有適用場景編排式更適合流程步驟清晰、需要強控制的業(yè)務(wù)協(xié)同式更適合服務(wù)間本來就依賴事件通信的架構(gòu)。Saga有一個必須接受的現(xiàn)實它只保證最終一致性不保證中間狀態(tài)的實時一致。比如下單流程創(chuàng)建訂單、扣庫存、扣余額三個步驟之間一定有短暫的時間窗口用戶可能看到訂單已創(chuàng)建但庫存還沒扣完。關(guān)鍵在于把這種中間狀態(tài)設(shè)計成可解釋、可恢復(fù)的而不是要求每個瞬間都強一致。這一步想不通Saga就推不下去。我推進Saga落地時踩過最大的坑是補償操作自身的冪等性。補償動作本身也可能因為網(wǎng)絡(luò)原因執(zhí)行兩次比如扣庫存的補償如果重復(fù)執(zhí)行庫存就被多加了一次。所以在設(shè)計補償操作時同樣要引入業(yè)務(wù)唯一號和去重機制否則補償反而成了新的數(shù)據(jù)破壞者。4.3 CQRS讀寫分離讀多寫多分開扛很多業(yè)務(wù)場景里讀操作和寫操作的頻率、數(shù)據(jù)模型、性能要求完全不同。拿訂單系統(tǒng)來說寫操作關(guān)心的是訂單狀態(tài)如何變更讀操作關(guān)心的是多條件組合查詢、列表篩選、匯總統(tǒng)計。如果都塞在同一套數(shù)據(jù)模型和服務(wù)接口里兩邊會互相拖累。CQRS命令查詢職責分離模式的核心就是把寫操作和讀操作分開建模、分開擴展。最輕量的CQRS實現(xiàn)是邏輯分離同一個服務(wù)里拆出兩套接口模型寫接口用Command語義讀接口用Query語義底層還是同一個數(shù)據(jù)庫。這種方案改動小適合想先引入CQRS思路試水的團隊。稍重一點的是物理分離寫服務(wù)擁有自己的業(yè)務(wù)庫讀服務(wù)維護一份從寫庫同步過來的投影數(shù)據(jù)讀操作直接查投影庫不再觸碰業(yè)務(wù)主庫讀壓力被徹底剝離。CQRS最常用的搭配是事件驅(qū)動。寫服務(wù)產(chǎn)生業(yè)務(wù)事件讀服務(wù)訂閱事件并更新自己的投影庫讀寫庫之間的同步是異步的。好處是讀操作完全不被寫操作阻塞壞處是數(shù)據(jù)存在延遲做不到強一致。所以CQRS適合的是讀多寫少、查詢條件復(fù)雜、讀模型和寫模型差異大的場景典型的是報表系統(tǒng)、搜索系統(tǒng)、訂單列表這類業(yè)務(wù)。如果是強實時一致性要求極高的簡單CRUD硬上CQRS只會徒增復(fù)雜度完全沒有必要。5. 異步與基礎(chǔ)設(shè)施流量如何流動5.1 事件驅(qū)動與異步消息模式解耦的終極武器事件驅(qū)動是微服務(wù)架構(gòu)里影響最深遠的模式之一。它的核心是服務(wù)之間不直接調(diào)用而是通過消息中間件傳遞事件。生產(chǎn)者只管發(fā)布事件消費者按需訂閱雙方不需要知道彼此的存在。這種設(shè)計對系統(tǒng)結(jié)構(gòu)的改變是根本性的。事件驅(qū)動帶來的好處非常直接。第一是解耦發(fā)送方不需要關(guān)心誰在處理自己發(fā)出去的事件新增一個下游服務(wù)完全不用改動上游代碼。第二是削峰填谷瞬時的大流量可以先涌進消息隊列消費者按自己能夠承受的節(jié)奏消費系統(tǒng)天然具備緩沖能力。第三是擴展性每個消費者可以獨立擴縮容處理速度跟不上就多開幾個實例不會影響其他服務(wù)。但事件驅(qū)動的代價也不小最大的代價是調(diào)用鏈變得不直觀。原來一個請求從A到B到C鏈路清晰現(xiàn)在變成了A發(fā)事件、B和C各自消費排查問題時很難直接從日志里串出完整鏈路。我過去在項目里遇到過一個很隱蔽的問題訂單創(chuàng)建后庫存服務(wù)偶爾扣不到庫存查了很久才發(fā)現(xiàn)是消費者消費消息時因為反序列化失敗不斷重試而重試消息又被路由到了錯誤的隊列。這類問題在同步調(diào)用時代幾乎不會出現(xiàn)。落地事件驅(qū)動至少要配套三樣基礎(chǔ)設(shè)施消息中間件、消息追蹤、死信隊列。消息追蹤給每個事件帶上全局鏈路ID方便把分散在各服務(wù)里的日志串起來死信隊列專門兜底所有消費失敗的消息避免消息悄無聲息地丟失。沒有這三樣配套事件驅(qū)動基本等于給自己埋雷。5.2 邊車模式把橫切能力下沉到基礎(chǔ)設(shè)施邊車模式在近幾年的服務(wù)網(wǎng)格實踐中越來越常見。它的思路是把日志、監(jiān)控、限流、鑒權(quán)這類橫切能力從業(yè)務(wù)進程里抽出來放到一個獨立進程里部署時和業(yè)務(wù)服務(wù)放在同一個Pod或同一臺機器上。這個獨立進程就像摩托車旁邊的邊車業(yè)務(wù)服務(wù)是主車通用能力由邊車來承擔。邊車模式最大的價值是讓業(yè)務(wù)團隊不再重復(fù)實現(xiàn)中間件能力。以前每個服務(wù)都要集成統(tǒng)一的監(jiān)控SDK、限流SDK升級一次SDK所有服務(wù)都要跟著發(fā)版。引入邊車之后這類能力統(tǒng)一由基礎(chǔ)設(shè)施團隊維護業(yè)務(wù)服務(wù)只需要保持簡單的通信協(xié)議即可。當然邊車也不是免費的。每個實例多一個進程資源開銷翻倍排障時也多了進程間的網(wǎng)絡(luò)交互。所以邊車模式更適合中大規(guī)模、服務(wù)數(shù)量多、團隊分工明確的組織小團隊幾十個服務(wù)規(guī)模硬上服務(wù)網(wǎng)格運維成本反而會超過收益。我的建議是先保持輕量SDK方案等服務(wù)數(shù)量增長到SDK升級真的成為痛點時再考慮邊車化。5.3 模式組合實戰(zhàn)一個下單業(yè)務(wù)用上五六個模式很多人單獨理解每個模式都覺得簡單但實際架構(gòu)里它們從來不是孤立使用的搭配組合才是常態(tài)。我用一個下單業(yè)務(wù)的例子串一遍你會看到模式是怎么協(xié)同工作的。用戶點擊下單請求先打到API網(wǎng)關(guān)網(wǎng)關(guān)完成鑒權(quán)和限流判斷請求是否超過整體閾值繼續(xù)把請求路由到訂單服務(wù)訂單服務(wù)先查注冊中心拿到庫存服務(wù)實例列表再發(fā)起遠程調(diào)用此時庫存服務(wù)如果已經(jīng)過載斷路器感知到連續(xù)失敗就會快速返回降級結(jié)果而不是讓調(diào)用方長時間等待為了防止網(wǎng)絡(luò)重試導(dǎo)致重復(fù)扣減下單接口用冪等表做去重訂單創(chuàng)建成功后通過消息中間件把訂單事件廣播給下游的積分服務(wù)和通知服務(wù)它們各自消費事件異步完成自己的業(yè)務(wù)整個過程涉及訂單庫和庫存庫跨服務(wù)的數(shù)據(jù)一致性則交給Saga模式編排補償。這個流程里網(wǎng)關(guān)、注冊發(fā)現(xiàn)、斷路器、冪等、事件驅(qū)動、Saga六個模式協(xié)同生效少任何一個環(huán)節(jié)系統(tǒng)的穩(wěn)定性都會打折扣。6. 模式速查表與避坑實錄6.1 10個模式速查表把前面講的10個模式整理成速查表方便查閱也方便在團隊評審架構(gòu)時快速對齊語言。模式一句話定位最常用落地形態(tài)最大坑點API網(wǎng)關(guān)/聚合器統(tǒng)一入口收口橫切邏輯網(wǎng)關(guān) BFF層邏輯堆砌網(wǎng)關(guān)變巨無霸服務(wù)注冊與發(fā)現(xiàn)動態(tài)感知實例變化注冊中心 客戶端負載均衡發(fā)現(xiàn)延遲、注冊中心抖動配置中心配置集中管理、動態(tài)發(fā)布遠程配置 本地緩存兜底配置中心掛導(dǎo)致服務(wù)掛斷路器失敗快返回防雪崩熔斷器中間件參數(shù)照抄模板慢調(diào)用沒統(tǒng)計限流與背壓控制入口流量保命優(yōu)先網(wǎng)關(guān)粗限流 服務(wù)細限流只限入口不管消息積壓冪等與重試重復(fù)請求不產(chǎn)生重復(fù)數(shù)據(jù)冪等表 唯一索引只用緩存校驗沒加數(shù)據(jù)庫約束獨立數(shù)據(jù)庫數(shù)據(jù)自治服務(wù)邊界清晰每服務(wù)獨立存儲跨服務(wù)查詢困難Saga跨服務(wù)事務(wù)的最終一致方案編排式/協(xié)同式補償補償操作本身沒有冪等CQRS讀寫分離按能力擴展投影庫 事件同步延遲導(dǎo)致實時一致性下降事件驅(qū)動服務(wù)間異步解耦、削峰消息隊列 死信隊列鏈路不可見消息丟失6.2 我踩過的三個坑第一個坑是網(wǎng)關(guān)配置了全局重試。當時為了提升接口成功率在網(wǎng)關(guān)層對所有請求開了自動重試結(jié)果下游某個服務(wù)因為一個隱藏的內(nèi)存問題頻繁返回錯誤重試流量把問題放大了好幾倍直到下游徹底被打掛才反應(yīng)過來?,F(xiàn)在的原則是網(wǎng)關(guān)層默認不重試只有接口冪等且失敗原因明確是超時的情況下才考慮重試而且重試次數(shù)最多一次。第二個坑是配置中心沒有做變更預(yù)覽。有一次運維手滑把一個服務(wù)的緩存開關(guān)配置改錯了配置中心立刻推送到所有實例服務(wù)行為瞬間變化等發(fā)現(xiàn)問題時線上已經(jīng)跑了一個多小時。后來我們在配置中心前加了一道發(fā)布審核和灰度發(fā)布機制重要配置走審批流并且支持按實例灰度推送先放一臺機器驗證再全量推送。第三個坑是Saga補償動作漏了日志。補償操作大多是低頻執(zhí)行平時根本走不到結(jié)果有一次真的觸發(fā)補償時才發(fā)現(xiàn)日志不夠詳細根本定位不了補償?shù)侥囊徊绞×恕:髞砦乙笏醒a償操作必須有獨立日志文件和完整的鏈路ID關(guān)鍵字段要打全。低頻操作反而更需要高質(zhì)量日志因為調(diào)試機會太少。6.3 選型原則按場景匹配不按流行度最后一個建議選型要跟著場景走不要跟著流行度走??吹絼e人在用服務(wù)網(wǎng)格就跟著上看到某篇文章推CQRS就覺得自己系統(tǒng)也該拆讀寫這是很多架構(gòu)問題的根源。你得先問自己的系統(tǒng)疼在哪里。調(diào)用鏈經(jīng)常超時先看斷路器和超時配置數(shù)據(jù)經(jīng)常對不上先看冪等和事務(wù)邊界告警總是半夜響先看限流和監(jiān)控。我在實際項目里還有一個體會模式是分階段引入的。早期服務(wù)數(shù)量不多重點是網(wǎng)關(guān)、注冊發(fā)現(xiàn)、配置中心這三板斧服務(wù)間交互變多后開始上斷路器、冪等、重試這些容錯手段等高并發(fā)場景出現(xiàn)了再逐步引入限流、消息隊列和CQRS。一上來就把所有模式堆滿只會讓排查問題的成本翻倍出問題時反而不知道是哪個環(huán)節(jié)的鍋。微服務(wù)沒有銀彈設(shè)計模式也不存在“用了就穩(wěn)”的魔法。它們的價值在于給你一套預(yù)先驗證過的思路讓你在遇到具體問題時不必從零開始摸索。上面這些坑我都是真金白銀交過學(xué)費的希望你能少走幾步彎路。如果這篇文章里的某個模式正好切中你最近在糾結(jié)的問題不妨把速查表那部分截下來落地的時候?qū)χ鴮φ找槐榇蟾怕誓軒湍惚荛_我當年踩過的那幾個坎。