證與面試精講:從模塊契約到分布式系統(tǒng)實(shí)戰(zhàn))
很多技術(shù)教程把“集成、驗(yàn)證”講成了兩個(gè)獨(dú)立的章節(jié)把“面試題”又講成另一套東西。但實(shí)際上做項(xiàng)目和準(zhǔn)備面試完全是一件事的兩面項(xiàng)目里怎么把模塊捏合在一起、怎么證明它靠譜面試官問(wèn)的就是你在這中間的每個(gè)決策和踩坑。這篇我直接把第07講“集成、驗(yàn)證與面試題精講”拆開(kāi)揉碎結(jié)合我這些年帶項(xiàng)目、做評(píng)審、面別人的經(jīng)驗(yàn)把集成階段的順序、驗(yàn)證手段的取舍、面試回答的套路一次說(shuō)清楚。適合正在做畢設(shè)、剛?cè)肼毿枰?dú)立負(fù)責(zé)模塊、以及準(zhǔn)備技術(shù)面試的開(kāi)發(fā)者參考內(nèi)容不繞彎子全是能直接用的方法。1. 集成的核心挑戰(zhàn)與設(shè)計(jì)思路1.1 集成不是把代碼拼在一起那么簡(jiǎn)單很多人對(duì)集成的理解是“大家把代碼往主干一合能跑起來(lái)就算完事”。我在實(shí)際項(xiàng)目里見(jiàn)過(guò)太多次這種想法的翻車現(xiàn)場(chǎng)每個(gè)人本地都跑得好好的一合到一起就各種報(bào)錯(cuò)接口對(duì)不上、字段名不一致、數(shù)據(jù)格式不對(duì)、環(huán)境配置互相覆蓋?;ㄔ谂胚@些破事上的時(shí)間往往比寫代碼本身還多。真正的集成核心要解決的是“模塊之間的契約問(wèn)題”。所謂契約不是你說(shuō)你傳個(gè)參數(shù)我收個(gè)參數(shù)這么簡(jiǎn)單而是包括接口的協(xié)議類型、數(shù)據(jù)字段的類型和含義、異常情況下的返回結(jié)構(gòu)、超時(shí)時(shí)間的約定、冪等性的保障甚至包括日志格式和追蹤標(biāo)識(shí)怎么傳遞。這些東西只要有一項(xiàng)沒(méi)約定清楚集成的時(shí)候就會(huì)爆發(fā)問(wèn)題。我之前帶某個(gè)模擬項(xiàng)目X的時(shí)候A同學(xué)負(fù)責(zé)用戶服務(wù)B同學(xué)負(fù)責(zé)訂單服務(wù)兩個(gè)人都是自己定義返回結(jié)構(gòu)。A同學(xué)用{code, message, data}B同學(xué)用{status, msg, result}。聯(lián)調(diào)的時(shí)候兩邊都認(rèn)為是對(duì)方的問(wèn)題實(shí)際上誰(shuí)都沒(méi)錯(cuò)錯(cuò)在剛開(kāi)始集成方案設(shè)計(jì)的時(shí)候沒(méi)有把統(tǒng)一返回結(jié)構(gòu)作為第一條約定寫進(jìn)文檔里。后來(lái)我們把所有服務(wù)的接口契約單獨(dú)抽出來(lái)評(píng)審類似的問(wèn)題基本在聯(lián)調(diào)前就消滅了。1.2 集成方案選型三種常見(jiàn)模式不同規(guī)模的項(xiàng)目集成的節(jié)奏完全不同。我梳理一下常見(jiàn)的三種模式你可以對(duì)照自己的項(xiàng)目情況選。第一種單體應(yīng)用內(nèi)部的模塊集成。這種其實(shí)最簡(jiǎn)單模塊之間直接方法調(diào)用不需要考慮網(wǎng)絡(luò)開(kāi)銷和序列化問(wèn)題。但它的陷阱在于模塊之間容易產(chǎn)生隱式依賴比如一個(gè)模塊直接改了另一個(gè)模塊的全局變量或者共享了一個(gè)靜態(tài)類。這種集成方式的重點(diǎn)在于“邊界意識(shí)”即使在一個(gè)進(jìn)程里也要把模塊之間的交互顯式化通過(guò)接口或者抽象類來(lái)解耦。第二種微服務(wù)架構(gòu)下的分布式集成。這種模式是目前大多數(shù)互聯(lián)網(wǎng)項(xiàng)目的標(biāo)配也是面試?yán)飭?wèn)得最多的。服務(wù)之間通過(guò)HTTP或者消息隊(duì)列通信需要考慮網(wǎng)絡(luò)延遲、部分失敗、冪等、分布式事務(wù)等一堆問(wèn)題。我剛帶項(xiàng)目的時(shí)候團(tuán)隊(duì)里的新人最容易犯的錯(cuò)誤是“同步調(diào)用一切”所有服務(wù)之間都是同步HTTP調(diào)用結(jié)果一個(gè)服務(wù)響應(yīng)慢了整個(gè)鏈路全部阻塞。后來(lái)我們引入消息隊(duì)列做異步解耦把不需要實(shí)時(shí)返回的操作全部改成異步整個(gè)系統(tǒng)的吞吐量上去了鏈路的穩(wěn)定性也好了很多。第三種第三方系統(tǒng)的外部集成。比如對(duì)接支付渠道、短信服務(wù)、地圖服務(wù)等。這種集成的關(guān)鍵在于適配層和防依賴腐化。你不能把第三方SDK直接在業(yè)務(wù)代碼里滿天飛一旦對(duì)方升級(jí)接口或者出了問(wèn)題你的業(yè)務(wù)代碼就得跟著遭殃。正確做法是包一層自己的適配器把第三方的東西隔離在邊界之外。1.3 模塊之間的約定接口設(shè)計(jì)先行很多項(xiàng)目失敗的原因不是因?yàn)榇a寫得差而是因?yàn)榻涌谠O(shè)計(jì)得隨意。接口設(shè)計(jì)這件事必須放在編碼之前做而且要經(jīng)過(guò)評(píng)審不是誰(shuí)寫了就算數(shù)。接口設(shè)計(jì)的基礎(chǔ)原則就那么幾條但每條都是血淚教訓(xùn)換來(lái)的。第一字段命名風(fēng)格統(tǒng)一要么全部小寫下劃線要么全部駝峰最怕混用。第二類型定義必須嚴(yán)謹(jǐn)比如金額必須用整數(shù)存分不能用浮點(diǎn)數(shù)存元心態(tài)崩沒(méi)崩過(guò)只有踩了坑的人知道。第三可選字段和必選字段要標(biāo)注清楚不能讓別人猜。第四錯(cuò)誤碼要有全局編號(hào)規(guī)則不能每個(gè)模塊自己從1開(kāi)始編號(hào)聯(lián)調(diào)的時(shí)候會(huì)出現(xiàn)兩個(gè)服務(wù)返回同一個(gè)code但表達(dá)的意思完全不同的奇葩場(chǎng)景。我建議你在項(xiàng)目啟動(dòng)初期就建一個(gè)接口契約文檔用表格維護(hù)接口路徑、請(qǐng)求方法、入?yún)⒆侄巍㈩愋?、是否必填、出參結(jié)構(gòu)、錯(cuò)誤碼枚舉、超時(shí)閾值。這個(gè)文檔看起來(lái)費(fèi)時(shí)間但它就是集成階段的地圖。沒(méi)有地圖的集成就是瞎撞有了地圖聯(lián)調(diào)只是照著走一遍而已。2. 集成實(shí)操全流程2.1 環(huán)境準(zhǔn)備與分支策略集成開(kāi)始之前先把環(huán)境問(wèn)題解決好否則后面每個(gè)環(huán)節(jié)都會(huì)堵車。最常見(jiàn)的問(wèn)題就是環(huán)境不統(tǒng)一有人用本地連測(cè)試庫(kù)有人用測(cè)試環(huán)境連開(kāi)發(fā)庫(kù)還有人直接在本地跑所有的依賴服務(wù)。這種混亂狀態(tài)下出的問(wèn)題排查起來(lái)非常痛苦因?yàn)楹芏鄷r(shí)候問(wèn)題根本不在代碼邏輯上而是環(huán)境配置差異導(dǎo)致的行為不一致。我個(gè)人的習(xí)慣是用一個(gè)docker-compose把所有依賴的基礎(chǔ)設(shè)施在本地跑起來(lái)數(shù)據(jù)庫(kù)、緩存、消息隊(duì)列統(tǒng)一版本每個(gè)人拉下來(lái)就能跑通一套完整環(huán)境。然后線上環(huán)境再單獨(dú)部署一套保持和本地的依賴版本一致。這樣至少能保證“在我本地是好的”這句話盡量少出現(xiàn)。分支策略沒(méi)什么花哨的推薦采用dev → test → release的環(huán)境配套策略。開(kāi)發(fā)分支dev開(kāi)啟日常集成的自動(dòng)化驗(yàn)證允許代碼頻繁合并測(cè)試分支test部署到測(cè)試環(huán)境供前后端人員、測(cè)試人員使用release分支只接受驗(yàn)證通過(guò)的版本合并。這樣每個(gè)分支的角色清晰誰(shuí)往哪個(gè)分支合并都有明確的依據(jù)不會(huì)出現(xiàn)順手把開(kāi)發(fā)中的半成品放到測(cè)試環(huán)境去坑隊(duì)友的情況。2.2 接口聯(lián)調(diào)的節(jié)奏與方法聯(lián)調(diào)不是一個(gè)端點(diǎn)坐下來(lái)對(duì)接口就完事它是有節(jié)奏的。從我?guī)Ф鄠€(gè)項(xiàng)目的經(jīng)驗(yàn)來(lái)看聯(lián)調(diào)應(yīng)該分三輪走。第一輪是“契約聯(lián)調(diào)”也可以叫“靜態(tài)聯(lián)調(diào)”。這一輪不涉及真實(shí)邏輯只驗(yàn)證接口的請(qǐng)求和返回結(jié)構(gòu)符合契約。最簡(jiǎn)單的方式是讓后端把接口文檔生成出來(lái)前端用Mock數(shù)據(jù)先跑一遍確認(rèn)字段名、嵌套結(jié)構(gòu)、類型都正確。這個(gè)階段如果能配合一個(gè)接口文檔平臺(tái)自動(dòng)生成Mock效率會(huì)高很多。很多前端抱怨后端改字段根本原因是這輪沒(méi)做好等真實(shí)接口出來(lái)了才發(fā)現(xiàn)字段對(duì)不上那當(dāng)然要返工。第二輪是“功能聯(lián)調(diào)”也就是跑真實(shí)場(chǎng)景。前端調(diào)真實(shí)接口后端也開(kāi)真實(shí)邏輯把完整的業(yè)務(wù)鏈路走通。這個(gè)階段的核心是“主鏈路優(yōu)先”先把核心流程跑通異常分支和邊界條件放在后面慢慢處理。我見(jiàn)過(guò)太多團(tuán)隊(duì)聯(lián)調(diào)時(shí)一個(gè)非核心的報(bào)錯(cuò)卡了兩天核心流程反而沒(méi)測(cè)最后上線日期到了核心鏈路還有一堆問(wèn)題。第三輪是“異常聯(lián)調(diào)”專門驗(yàn)證異常場(chǎng)景。超時(shí)、限流、服務(wù)不可用、重復(fù)請(qǐng)求這些都要在這一輪覆蓋到。有人會(huì)問(wèn)這些不是測(cè)試該做的嗎我必須強(qiáng)調(diào)一下測(cè)試確實(shí)會(huì)做但作為開(kāi)發(fā)者在聯(lián)調(diào)階段自己先過(guò)一輪異常場(chǎng)景不僅能發(fā)現(xiàn)自己代碼里的防御邏輯缺漏也能順手驗(yàn)證降級(jí)方案是否真的生效而不是等到測(cè)試環(huán)境才靠運(yùn)氣發(fā)現(xiàn)。2.3 數(shù)據(jù)層集成的坑數(shù)據(jù)層集成是整個(gè)集成過(guò)程中最容易被低估的部分實(shí)際上它排坑耗時(shí)占比和難度遠(yuǎn)高于接口聯(lián)調(diào)。第一個(gè)坑數(shù)據(jù)庫(kù)表結(jié)構(gòu)變更沒(méi)有同步機(jī)制。兩個(gè)服務(wù)分別維護(hù)自己負(fù)責(zé)的表A服務(wù)改了某張表的名字B服務(wù)還在用舊名查詢一上線就一片雪上加霜。解決辦法是在集成方案里明確數(shù)據(jù)字典的變更通知流程任何表結(jié)構(gòu)變更必須同步到所有依賴方且要有兼容過(guò)渡期。第二個(gè)坑分布式事務(wù)。我見(jiàn)過(guò)很多剛接觸微服務(wù)的人一聽(tīng)到分布式事務(wù)就頭大然后要么選擇“不做分布式事務(wù)”要么引入一堆分布式事務(wù)框架把系統(tǒng)搞得很重。實(shí)際上大多數(shù)業(yè)務(wù)場(chǎng)景根本不需要強(qiáng)一致阿里巴巴的經(jīng)典分類思路就是把“剛性事務(wù)”降級(jí)為“柔性事務(wù)”使用本地消息表或者事務(wù)消息去保證最終一致性。比如一個(gè)下單操作同時(shí)要扣庫(kù)存和生成訂單正確做法是先提交一個(gè)本地消息由異步任務(wù)去扣庫(kù)存扣成功的才把訂單狀態(tài)置為已支付。這個(gè)思路比引入強(qiáng)一致框架簡(jiǎn)潔得多也和大部分項(xiàng)目的實(shí)際需求匹配。第三個(gè)坑數(shù)據(jù)遷移和數(shù)據(jù)補(bǔ)償。歷史數(shù)據(jù)遷移到新表結(jié)構(gòu)之后業(yè)務(wù)代碼是否還兼容數(shù)據(jù)清洗跑完了要不要做比對(duì)補(bǔ)償任務(wù)掛在哪個(gè)調(diào)度平臺(tái)上失敗了重試幾次這些事不弄清楚就盲上集成生產(chǎn)環(huán)境的數(shù)據(jù)就會(huì)給你顏色看。3. 驗(yàn)證的價(jià)值與設(shè)計(jì)3.1 從單元測(cè)試到端到端測(cè)試的驗(yàn)證體系驗(yàn)證這件事不能只靠最后手工點(diǎn)一遍那不是驗(yàn)證那是碰運(yùn)氣。我這里的驗(yàn)證體系從低到高分為四層單元測(cè)試、接口測(cè)試、集成測(cè)試、端到端測(cè)試。單元測(cè)試驗(yàn)證一個(gè)函數(shù)、一個(gè)方法在給定輸入下產(chǎn)出正確輸出重點(diǎn)是覆蓋復(fù)雜邏輯分支而不是為了湊覆蓋率把每個(gè)getter、setter都測(cè)一遍。接口測(cè)試驗(yàn)證一個(gè)服務(wù)對(duì)外暴露的接口行為符合契約請(qǐng)求參數(shù)的有效性、返回碼的語(yǔ)義、異常分支的狀態(tài)流轉(zhuǎn)都在這里驗(yàn)證。集成測(cè)試驗(yàn)證多個(gè)服務(wù)協(xié)同是否正常通常是在測(cè)試環(huán)境拉起多服務(wù)走真實(shí)的數(shù)據(jù)流和調(diào)用鏈。端到端測(cè)試站在用戶視角模擬完整業(yè)務(wù)流程比如一個(gè)用戶從注冊(cè)到下單到支付到查詢訂單的全鏈路。這四層不是都要寫很多代碼而是按項(xiàng)目情況取舍。如果你是一個(gè)單體小項(xiàng)目單元測(cè)試加端到端測(cè)試就覆蓋得差不多了如果你是一個(gè)中大型分布式項(xiàng)目接口測(cè)試和集成測(cè)試的價(jià)值會(huì)更明顯。關(guān)鍵不是層數(shù)多而是“每一層驗(yàn)證的對(duì)象明確、反饋迅速”出了問(wèn)題能快速定位是哪一層的鍋。3.2 自動(dòng)化測(cè)試的取舍與經(jīng)濟(jì)性我必須潑一盆冷水自動(dòng)化測(cè)試不是越多越好。很多團(tuán)隊(duì)把覆蓋率當(dāng)成KPI強(qiáng)行把覆蓋率從40%拉到80%結(jié)果代碼里全是低價(jià)值的斷言真正核心的鏈路反而沒(méi)人測(cè)。這種自動(dòng)化是虛假繁榮。我自己的取舍標(biāo)準(zhǔn)有三個(gè)。第一核心邏輯必須測(cè)。涉及金額計(jì)算、狀態(tài)流轉(zhuǎn)、權(quán)限判斷、數(shù)據(jù)唯一性約束的邏輯不測(cè)就是在賭。第二復(fù)雜分支必須測(cè)。一個(gè)方法里有五個(gè)if嵌套你不給每個(gè)分支寫測(cè)試后面的維護(hù)者改一個(gè)條件就要提心吊膽。第三重構(gòu)頻繁的模塊必須測(cè)。這是為未來(lái)的自己省時(shí)間重構(gòu)完跑一遍測(cè)試比手動(dòng)點(diǎn)功能點(diǎn)效率高得多。有一個(gè)層面很少被提到就是“回滾保護(hù)”。自動(dòng)化測(cè)試在工作實(shí)踐中最大的價(jià)值不是你寫完代碼后跑一遍通過(guò)了而是你或者別人某一天改代碼的時(shí)候測(cè)試能把改壞的功能揪出來(lái)。這個(gè)“回歸保護(hù)”的價(jià)值往往比驗(yàn)證當(dāng)次功能是否正確更大。3.3 性能、異常與安全性驗(yàn)證集成階段最容易忽略的就是性能驗(yàn)證。很多系統(tǒng)在少量數(shù)據(jù)、低并發(fā)測(cè)試下看起來(lái)一切正常一上線就扛不住流量接口一個(gè)接一個(gè)超時(shí)。性能驗(yàn)證不一定要用復(fù)雜的壓測(cè)工具最基本的一個(gè)壓測(cè)工具用起來(lái)模擬比預(yù)估峰值高的并發(fā)數(shù)跑一跑關(guān)鍵接口觀察響應(yīng)時(shí)間的變化趨勢(shì)和錯(cuò)誤率能暴露出很多設(shè)計(jì)上的瓶頸比如數(shù)據(jù)庫(kù)連接池不夠、某個(gè)接口存在嚴(yán)重的N1查詢、緩存沒(méi)生效等。異常驗(yàn)證也要專門設(shè)計(jì)。熔斷、降級(jí)、重試、超時(shí)這四件套不能只在代碼評(píng)審里講“我加了重試”得上線驗(yàn)證重試多少次、退避策略是什么、會(huì)不會(huì)打爆下游。最經(jīng)典的例子就是重試風(fēng)暴A服務(wù)超時(shí)后立刻重試3次B服務(wù)也超時(shí)也重試3次C服務(wù)直接被打掛。這種問(wèn)題只有真正壓過(guò)一次或者演練一次才心里有數(shù)。安全驗(yàn)證這塊接口層面的鑒權(quán)、越權(quán)、SQL注入、敏感數(shù)據(jù)脫敏測(cè)試用例里必須覆蓋。我見(jiàn)過(guò)一個(gè)項(xiàng)目在集成測(cè)試階段發(fā)現(xiàn)訂單詳情的接口只要把訂單ID從1改成2就能查到別人的訂單原因就是僅校驗(yàn)登錄態(tài)、未校驗(yàn)數(shù)據(jù)歸屬。這類問(wèn)題在功能測(cè)試?yán)锿ǔy(cè)不出來(lái)必須專門設(shè)計(jì)越權(quán)用例。4. 面試題精講從知識(shí)到實(shí)戰(zhàn)4.1 面試官真正想考察的三種能力面試題尤其是集成和驗(yàn)證相關(guān)的問(wèn)題表面上考的是知識(shí)點(diǎn)實(shí)際上考察的是三種底層能力。第一種整體設(shè)計(jì)能力。面試官給你一個(gè)場(chǎng)景問(wèn)你“怎么做集成方案”不是要你背誦微服務(wù)理論而是你能不能cover住全局哪些是核心流程、哪些可以異步化、接口怎么劃分、異常怎么兜底。這個(gè)能力來(lái)自于你真正帶過(guò)模塊、處理過(guò)集成沖突的經(jīng)驗(yàn)而不是看了幾篇技術(shù)博客就能吹出來(lái)的。第二種權(quán)衡與決策能力。面試?yán)锝?jīng)常問(wèn)“你項(xiàng)目里為什么用消息隊(duì)列不用直接調(diào)用”“為什么用最終一致性而不用強(qiáng)一致”本質(zhì)上就是看你有沒(méi)有做過(guò)思考、有沒(méi)有在幾種方案之間比較過(guò)優(yōu)缺?;卮饡r(shí)最忌諱只講好處不講代價(jià)任何技術(shù)選型都有代價(jià)能講清楚代價(jià)才說(shuō)明你真的想過(guò)。第三種問(wèn)題排查能力。面試官問(wèn)“你線上遇到的最難排查的問(wèn)題是什么”不是真想聽(tīng)一個(gè)高分故事而是看你的排查思路是否系統(tǒng)有沒(méi)有先懷疑環(huán)境、再懷疑配置、再看日志、再加監(jiān)控最后定位到代碼而不是瞎猜。4.2 高頻核心問(wèn)題拆解我整理了幾個(gè)集成與驗(yàn)證方向的高頻面試題每個(gè)都附上回答思路供你參考。問(wèn)題一服務(wù)A調(diào)用服務(wù)B超時(shí)你會(huì)怎么排查很多人的第一反應(yīng)是“看B的日志”這個(gè)沒(méi)錯(cuò)但不夠系統(tǒng)。我的回答會(huì)分層次先確認(rèn)超時(shí)是發(fā)生在連接階段還是讀取階段連接階段大概率是網(wǎng)絡(luò)或者服務(wù)不可用讀取階段大概率是B處理慢或者線程池被打滿再看A的重試配置會(huì)不會(huì)放大問(wèn)題A的超時(shí)時(shí)間設(shè)置是否合理然后看B的監(jiān)控面板接口耗時(shí)分布、線程池活躍度、數(shù)據(jù)庫(kù)慢查詢最后根據(jù)現(xiàn)象判斷是代碼性能問(wèn)題、資源不足還是依賴下游慢導(dǎo)致的雪崩?;卮鸢雅挪槁窂绞崂硗瓿杀戎苯咏o出一個(gè)“正確答案”更能打動(dòng)面試官。問(wèn)題二你們項(xiàng)目的集成測(cè)試是怎么做的這個(gè)問(wèn)題要結(jié)合自己的項(xiàng)目具體說(shuō)不能空談理論。你可以從環(huán)境搭建、數(shù)據(jù)準(zhǔn)備、自動(dòng)化手段、人工補(bǔ)充這四個(gè)角度展開(kāi)。重點(diǎn)突出你曾經(jīng)在集成階段發(fā)現(xiàn)過(guò)一個(gè)有代表性的問(wèn)題然后是怎么定位、怎么解決的。這類帶真實(shí)細(xì)節(jié)的回答比背誦測(cè)試金字塔定義有用一百倍。問(wèn)題三服務(wù)注冊(cè)發(fā)現(xiàn)、配置中心在集成中扮演什么角色這個(gè)問(wèn)題考的是你對(duì)“基礎(chǔ)設(shè)施”的理解。我的思路是這些組件解決的是“動(dòng)態(tài)變化”的問(wèn)題——服務(wù)實(shí)例上下線、配置變更、路由流量調(diào)整。集成過(guò)程中服務(wù)地址會(huì)經(jīng)常變不可能靠手工改配置維護(hù)配置中心讓不同環(huán)境的配置隔離清晰避免把測(cè)試環(huán)境的配置帶到線上注冊(cè)發(fā)現(xiàn)讓系統(tǒng)對(duì)實(shí)例變化敏感故障時(shí)能摘流量?;卮饡r(shí)能結(jié)合具體場(chǎng)景講一個(gè)故障案例會(huì)更有說(shuō)服力。4.3 場(chǎng)景設(shè)計(jì)題的回答框架面試中的場(chǎng)景設(shè)計(jì)題比如“讓你設(shè)計(jì)一個(gè)訂單超時(shí)關(guān)閉系統(tǒng)”本質(zhì)上就是讓你現(xiàn)場(chǎng)做集成方案。我摸索出一個(gè)通用的四步回答框架屢試不爽。第一步確認(rèn)約束問(wèn)清楚關(guān)鍵場(chǎng)景。比如單量多大、超時(shí)時(shí)間多久、是否需要實(shí)時(shí)性、失敗后如何處理。這一步很重要很多人上來(lái)就畫架構(gòu)圖結(jié)果跟面試官期望的方向完全不一樣。第二步識(shí)別核心鏈路畫主線。從用戶下單開(kāi)始到超時(shí)檢測(cè)到狀態(tài)變更到后續(xù)補(bǔ)償把主干看清楚。第三步拆解選型逐個(gè)說(shuō)明“為什么”。延遲消息隊(duì)列、定時(shí)任務(wù)輪詢、時(shí)間輪各有什么優(yōu)劣為什么在你的場(chǎng)景里選這個(gè)。每說(shuō)一個(gè)選型主動(dòng)說(shuō)出它的代價(jià)。第四步談異常降級(jí)展示邊界思考。消息積壓怎么辦、服務(wù)重啟任務(wù)丟失怎么辦、重復(fù)關(guān)閉訂單怎么冪等。能主動(dòng)把這些講出來(lái)就是加分項(xiàng)。這個(gè)框架的精髓在于“把問(wèn)題拆小”。一個(gè)很宏大的設(shè)計(jì)題拆成約束確認(rèn)、主鏈路、選型、異常四個(gè)部分之后每個(gè)部分的復(fù)雜度都會(huì)降一個(gè)級(jí)別回答起來(lái)從容得多。4.4 手寫代碼題與HR面的關(guān)鍵提醒手寫代碼題主要集中在接口實(shí)現(xiàn)、線程安全、數(shù)據(jù)一致性這類細(xì)節(jié)上。我見(jiàn)過(guò)不少面試者架構(gòu)講得頭頭是道一寫到單例模式或者多線程就磕磕絆絆。核心原因在于“讀代碼”和“寫代碼”是兩種不同的能力平時(shí)自己上手敲得少。建議你在準(zhǔn)備面試前把數(shù)據(jù)結(jié)構(gòu)、并發(fā)編程、緩存一致性這些基礎(chǔ)代碼題手寫練一遍不是看一眼就過(guò)是真正在編輯器里敲出來(lái)然后運(yùn)行驗(yàn)證。到了HR面很多人以為要收起技術(shù)感其實(shí)也不對(duì)。HR面更關(guān)注的是你的溝通表達(dá)、做事方式、價(jià)值觀。比如“你最近遇到的一個(gè)困難是什么”你沒(méi)有真實(shí)經(jīng)歷過(guò)的話編出來(lái)的故事一聽(tīng)就假面試官稍微追問(wèn)細(xì)枝末節(jié)就露餡。誠(chéng)實(shí)講一件小事說(shuō)清楚背景、思路、行動(dòng)和結(jié)果比編造一個(gè)跌宕起伏的劇本可靠得多。還有一個(gè)要點(diǎn)是HR面不要只講技術(shù)還要體現(xiàn)你如何推動(dòng)協(xié)作、如何向上同步風(fēng)險(xiǎn)、如何在團(tuán)隊(duì)里處理分歧這些軟素質(zhì)在后面的團(tuán)隊(duì)協(xié)作中往往比技術(shù)本身更關(guān)鍵。5. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄5.1 集成過(guò)程中的典型問(wèn)題速查表這一節(jié)我把這些年集成階段遇到過(guò)的、具有代表性的問(wèn)題按“現(xiàn)象—原因—解法”的維度整理成速查表你可以直接拿去做排查手冊(cè)?,F(xiàn)象典型原因解決思路接口聯(lián)調(diào)時(shí)字段總是對(duì)不上接口契約文檔與實(shí)際代碼不同步用接口平臺(tái)維護(hù)契約代碼變更觸發(fā)文檔自動(dòng)更新強(qiáng)制評(píng)審測(cè)試環(huán)境一會(huì)好一會(huì)壞多人共用環(huán)境導(dǎo)致互相影響引入環(huán)境隔離或租戶隔離重要流程在獨(dú)立環(huán)境驗(yàn)證服務(wù)啟動(dòng)報(bào)端口占用本地起多個(gè)服務(wù)實(shí)例沖突用docker-compose統(tǒng)一規(guī)劃端口映射避免隨機(jī)端口調(diào)用下游偶爾超時(shí)線程池配置過(guò)小或下游GC停頓壓測(cè)確認(rèn)線程池水位優(yōu)化GC參數(shù)并配置合理的超時(shí)重試數(shù)據(jù)庫(kù)死鎖偶現(xiàn)多個(gè)事務(wù)加鎖順序不一致統(tǒng)一加鎖順序優(yōu)化事務(wù)范圍必要時(shí)引入分布式鎖數(shù)據(jù)重復(fù)插入接口未實(shí)現(xiàn)冪等增加冪等表或唯一索引同一請(qǐng)求用唯一業(yè)務(wù)鍵去重接口返回成功但業(yè)務(wù)未執(zhí)行異步消息丟失或消費(fèi)失敗增加消息本地表、消費(fèi)記錄表和補(bǔ)償任務(wù)日志串聯(lián)不上缺少統(tǒng)一的traceId傳遞在網(wǎng)關(guān)生成traceId通過(guò)攔截器透?jìng)鞯剿邢掠握{(diào)用5.2 排查問(wèn)題的三段式方法排查問(wèn)題最忌“猜測(cè)驅(qū)動(dòng)”也就是“我猜是這里有問(wèn)題改一下試試不行再改”。這種碰運(yùn)氣式的排查碰到簡(jiǎn)單問(wèn)題還行碰到復(fù)雜問(wèn)題會(huì)一直原地打轉(zhuǎn)最后改出一堆新bug。我自己的排查動(dòng)作基本固定為三段。第一段收集證據(jù)。先把日志、監(jiān)控、鏈路追蹤、報(bào)錯(cuò)堆棧全部拉出來(lái)確認(rèn)問(wèn)題的現(xiàn)象到底是什么。很多時(shí)候開(kāi)發(fā)者在口頭描述“它掛了”時(shí)掛的模式千差萬(wàn)別是超時(shí)、是報(bào)錯(cuò)、是數(shù)據(jù)錯(cuò)、還是性能慢必須用數(shù)據(jù)把現(xiàn)象釘死。第二段縮小范圍。用二分法把可能出問(wèn)題的環(huán)節(jié)縮小到某一個(gè)確定的層級(jí)。比如前端報(bào)一個(gè)接口500先確認(rèn)是網(wǎng)關(guān)攔截了還是服務(wù)拋異常再確認(rèn)是Controller層的問(wèn)題還是Service層的問(wèn)題再確認(rèn)是代碼邏輯還是數(shù)據(jù)庫(kù)層面。每縮小一步都要有一個(gè)證據(jù)支撐。第三段驗(yàn)證根因。找到嫌疑點(diǎn)后不要急著改代碼。先在測(cè)試環(huán)境復(fù)現(xiàn)一次確認(rèn)根因成立再動(dòng)手修。修完之后再把修復(fù)前后的現(xiàn)象對(duì)比一次防止改出回歸問(wèn)題。這套流程看起來(lái)很樸素但效率極高能保證你所有的改動(dòng)都是基于事實(shí)而不是想象。5.3 我在實(shí)際集成中養(yǎng)成的幾個(gè)習(xí)慣最后一個(gè)部分分享幾個(gè)我一直在用的習(xí)慣它們不是某個(gè)具體的工具或技巧而是做事的方法。第一個(gè)習(xí)慣永遠(yuǎn)在開(kāi)始前留出15%的緩沖時(shí)間。集成和驗(yàn)證階段的不可預(yù)測(cè)性遠(yuǎn)超編碼階段你以為一天能搞定的聯(lián)調(diào)實(shí)操中因?yàn)榄h(huán)境問(wèn)題耗掉三天也不罕見(jiàn)。所以做項(xiàng)目排期時(shí)集成階段的時(shí)間一定要留足緩沖不然后面要么趕工刪功能要么熬夜填坑。第二個(gè)習(xí)慣集成階段每天跑一遍全量自動(dòng)化測(cè)試。不要等到提交前才跑那時(shí)候發(fā)現(xiàn)問(wèn)題往往已經(jīng)晚了。每天跑一次能盡早發(fā)現(xiàn)環(huán)境變動(dòng)或者新改動(dòng)對(duì)已有功能的破壞。第三個(gè)習(xí)慣任何一次線上問(wèn)題的修復(fù)都必須輸出復(fù)盤記錄。不用寫長(zhǎng)篇大論就寫清楚現(xiàn)象、根因、修復(fù)方案、預(yù)防措施。這樣每一篇都是一份寶貴的排查資料下次遇到類似問(wèn)題直接查自己的復(fù)盤記錄基本上能省掉一半的排查時(shí)間。6. 從項(xiàng)目到面試的認(rèn)知升級(jí)回到第07講的主題“集成、驗(yàn)證與面試題精講”你會(huì)發(fā)現(xiàn)集成考驗(yàn)的是你對(duì)模塊邊界的理解能力驗(yàn)證考驗(yàn)的是你對(duì)確定性的執(zhí)著程度面試題考驗(yàn)的是你有沒(méi)有把前兩者內(nèi)化成自己的經(jīng)驗(yàn)。我在實(shí)際帶項(xiàng)目時(shí)最欣慰的時(shí)刻不是功能上線而是團(tuán)隊(duì)里有人開(kāi)始主動(dòng)問(wèn)我“我們這兩個(gè)模塊的接口契約是不是該先評(píng)審一下”或者“這個(gè)異常的測(cè)試用例要不要補(bǔ)上”。當(dāng)一個(gè)人開(kāi)始主動(dòng)關(guān)注集成邊界、主動(dòng)驗(yàn)證異常分支時(shí)他就已經(jīng)不是在“完成任務(wù)”而是在“把一件事做成”。對(duì)于正在準(zhǔn)備面試的人我的建議是不要背題要把每一個(gè)高頻問(wèn)題拿到自己的項(xiàng)目里去對(duì)照一遍問(wèn)自己我的項(xiàng)目里這個(gè)場(chǎng)景是怎么做的如果重新來(lái)一次我會(huì)做哪些不同選擇這樣準(zhǔn)備出來(lái)的回答每一句都來(lái)自真實(shí)經(jīng)驗(yàn)面試官一聽(tīng)就感覺(jué)得到。最后再分享一個(gè)小技巧面試?yán)锊还苡龅绞裁磫?wèn)題先停頓三秒再回答。這三秒不是用來(lái)緊張的是用來(lái)整理結(jié)構(gòu)的先說(shuō)結(jié)論、再講理由、最后補(bǔ)充例外情況。這個(gè)習(xí)慣會(huì)讓你所有的回答都顯得有條理、有深度。