戰(zhàn):從流程設(shè)計(jì)到異常處理的完整指南)
1. 接口編排到底在解決什么問(wèn)題先說(shuō)個(gè)我自己的經(jīng)歷。早幾年做系統(tǒng)集成最怕的就是“接口滿(mǎn)天飛”的項(xiàng)目——第三方支付要調(diào)、內(nèi)部訂單系統(tǒng)要調(diào)、消息隊(duì)列要推、數(shù)據(jù)庫(kù)偶爾還要直查一遍。那時(shí)候最原始的做法就是挨個(gè)寫(xiě)代碼一個(gè)流程串十幾個(gè)接口代碼里全是回調(diào)嵌套和異常分支改一個(gè)參數(shù)要翻半天邏輯測(cè)試環(huán)境跟生產(chǎn)環(huán)境行為還不一致排錯(cuò)排到懷疑人生。后來(lái)?yè)Q到接口開(kāi)發(fā)平臺(tái)用上了接口編排才真正理解這個(gè)設(shè)計(jì)的價(jià)值它把“寫(xiě)代碼串聯(lián)接口”這件事變成了“用可視化流程把已有接口組裝起來(lái)”。接口編排簡(jiǎn)單說(shuō)就是在一個(gè)平臺(tái)上把你已經(jīng)開(kāi)發(fā)好的、或者第三方提供的接口當(dāng)成積木塊通過(guò)拖拽、連線(xiàn)、配置參數(shù)的方式組合成一條完整的業(yè)務(wù)處理鏈路。典型的場(chǎng)景比如下單后自動(dòng)同步庫(kù)存、同時(shí)通知物流和財(cái)務(wù)再比如用戶(hù)注冊(cè)后自動(dòng)創(chuàng)建賬號(hào)、發(fā)歡迎短信、初始化默認(rèn)配置。以前這些邏輯散落在各個(gè)服務(wù)里現(xiàn)在集中在一個(gè)編排流程里一個(gè)入口觸發(fā)整條鏈路按順序跑完。適合誰(shuí)來(lái)用兩類(lèi)人。一類(lèi)是后端開(kāi)發(fā)用它來(lái)減少重復(fù)的膠水代碼把精力留在核心業(yè)務(wù)邏輯上另一類(lèi)是實(shí)施顧問(wèn)、測(cè)試人員或者偏運(yùn)維的同事他們不一定精通編程但通過(guò)可視化編排也能搭出可用的接口流程改配置也不需要等開(kāi)發(fā)排期。實(shí)際上這兩類(lèi)人配合起來(lái)效率最高開(kāi)發(fā)負(fù)責(zé)把原子接口維護(hù)好業(yè)務(wù)或?qū)嵤┴?fù)責(zé)在平臺(tái)上編排。這里面有個(gè)關(guān)鍵認(rèn)知接口編排不是要取代代碼而是把“調(diào)用關(guān)系”和“數(shù)據(jù)處理”這些相對(duì)標(biāo)準(zhǔn)化的部分從代碼里抽出來(lái)。代碼里剩下的應(yīng)該是復(fù)雜的算法、強(qiáng)事務(wù)性操作和需要深度定制的邏輯。平臺(tái)擅長(zhǎng)處理的是“多個(gè)接口按什么順序執(zhí)行、入?yún)⒃趺唇M裝、出參怎么映射、失敗時(shí)怎么降級(jí)”而這些東西恰恰是以前最容易出bug、最耗維護(hù)成本的部分。所以我把這篇文章定位成一份實(shí)操分享從編排流程的設(shè)計(jì)思路到核心配置項(xiàng)的含義再到完整跑通一個(gè)流程的步驟最后把我在實(shí)際項(xiàng)目中踩過(guò)的坑挨個(gè)列出來(lái)。內(nèi)容基于我用過(guò)的幾款接口開(kāi)發(fā)平臺(tái)的通用設(shè)計(jì)思路來(lái)寫(xiě)具體按鈕位置和字段叫法可能稍有差異但底層邏輯是共通的照著這個(gè)思路去套你手頭的平臺(tái)基本不會(huì)跑偏。2. 編排流程的整體設(shè)計(jì)與思路拆解2.1 先拆業(yè)務(wù)鏈路再動(dòng)手搭流程很多新手拿到需求第一反應(yīng)就是打開(kāi)編排畫(huà)布開(kāi)始拖節(jié)點(diǎn)。這個(gè)順序其實(shí)是反的。正確做法是先畫(huà)一張業(yè)務(wù)鏈路圖明確這個(gè)流程從哪個(gè)事件開(kāi)始、中間經(jīng)過(guò)哪幾步、每一步需要什么數(shù)據(jù)、最終要把結(jié)果送到哪里。我習(xí)慣用一張表格來(lái)做這件事不需要復(fù)雜工具Excel或者紙上畫(huà)都行。比如一個(gè)典型的“用戶(hù)下單后處理流程”步驟觸發(fā)來(lái)源動(dòng)作依賴(lài)數(shù)據(jù)產(chǎn)出數(shù)據(jù)1訂單服務(wù)回調(diào)接收訂單信息訂單號(hào)、用戶(hù)ID、商品編碼、金額標(biāo)準(zhǔn)化后的訂單數(shù)據(jù)2-查詢(xún)用戶(hù)信息用戶(hù)ID用戶(hù)手機(jī)號(hào)、會(huì)員等級(jí)3-扣減庫(kù)存商品編碼、購(gòu)買(mǎi)數(shù)量扣減結(jié)果4-發(fā)送短信通知用戶(hù)手機(jī)號(hào)、訂單號(hào)發(fā)送結(jié)果5-如果扣庫(kù)存失敗錯(cuò)誤信息回滾訂單狀態(tài)這張表一旦拉出來(lái)流程骨架就清楚了。你會(huì)發(fā)現(xiàn)第1步到第4步是順序執(zhí)行第5步是異常分支。再往下才需要考慮哪些節(jié)點(diǎn)之間要傳參數(shù)、參數(shù)名怎么映射、某個(gè)接口超時(shí)了是重試還是直接走失敗分支。把這個(gè)前置工作做扎實(shí)后面在畫(huà)布上操作的時(shí)間能省一大半。拆鏈路的時(shí)候還要注意一個(gè)原則編排流程里只放“必要”的節(jié)點(diǎn)。有些數(shù)據(jù)轉(zhuǎn)換邏輯能放在上游接口內(nèi)部解決的就不要讓編排層去處理。比如上游接口已經(jīng)能返回標(biāo)準(zhǔn)JSON你就沒(méi)有必要在編排里寫(xiě)一個(gè)腳本來(lái)重新拼裝字段。編排層的職責(zé)是“路由和組裝”不是“計(jì)算和加工”。所有復(fù)雜計(jì)算盡量下沉到具體接口實(shí)現(xiàn)里不然編排流程會(huì)越來(lái)越臃腫最后變成一個(gè)誰(shuí)都不敢動(dòng)的巨型流程圖。2.2 編排節(jié)點(diǎn)選型不同平臺(tái)的通用能力每個(gè)接口開(kāi)發(fā)平臺(tái)的編排畫(huà)布上節(jié)點(diǎn)的叫法和分類(lèi)可能不一樣但核心能力基本逃不出這幾類(lèi)。我把它們整理一下方便你對(duì)照自己手頭的平臺(tái)。第一類(lèi)叫觸發(fā)器節(jié)點(diǎn)也就是流程的入口。常見(jiàn)的有HTTP觸發(fā)、定時(shí)觸發(fā)、消息觸發(fā)MQ消費(fèi)。HTTP觸發(fā)最常見(jiàn)外部系統(tǒng)通過(guò)POST或GET請(qǐng)求把數(shù)據(jù)送進(jìn)來(lái)流程就開(kāi)始了。定時(shí)觸發(fā)用于跑批類(lèi)任務(wù)比如每天凌晨同步一次數(shù)據(jù)。消息觸發(fā)適合異步場(chǎng)景系統(tǒng)之間通過(guò)消息隊(duì)列解耦。選哪個(gè)觸發(fā)器取決于你這條流程是被誰(shuí)調(diào)用、以什么方式調(diào)用。第二類(lèi)叫接口調(diào)用節(jié)點(diǎn)。這是編排的主體你可以選擇一個(gè)已注冊(cè)的接口然后配置入?yún)⒑统鰠?。入?yún)?lái)源可以是觸發(fā)器帶來(lái)的原始數(shù)據(jù)也可以是前面某個(gè)節(jié)點(diǎn)產(chǎn)生的輸出。出參會(huì)存在一個(gè)“變量空間”里供后續(xù)節(jié)點(diǎn)引用。接口節(jié)點(diǎn)往往還有超時(shí)時(shí)間、重試次數(shù)、失敗處理策略三個(gè)配置這三個(gè)配置在不同平臺(tái)上的默認(rèn)值差異很大強(qiáng)烈建議搭流程的時(shí)候就調(diào)整好而不是用默認(rèn)值一把梭。第三類(lèi)叫邏輯控制節(jié)點(diǎn)。包括條件判斷IF/ELSE、循環(huán)FOR/WHILE、并行調(diào)用、分支合并等。條件判斷是編排里最常用的比如判斷上一步返回的code字段是不是success是走A分支不是走B分支。循環(huán)用來(lái)處理列表數(shù)據(jù)比如批量查詢(xún)一批訂單的物流狀態(tài)。并行調(diào)用適合那些相互不依賴(lài)的接口比如同時(shí)查庫(kù)存和查用戶(hù)信息能顯著縮短總耗時(shí)。第四類(lèi)叫數(shù)據(jù)轉(zhuǎn)換節(jié)點(diǎn)。這個(gè)節(jié)點(diǎn)允許你寫(xiě)一小段腳本對(duì)數(shù)據(jù)做格式化、字段映射、拼接、拆分等操作。常見(jiàn)的實(shí)現(xiàn)有JavaScript腳本或Groovy腳本。數(shù)據(jù)轉(zhuǎn)換節(jié)點(diǎn)是編排靈活性的關(guān)鍵沒(méi)有它節(jié)點(diǎn)之間的參數(shù)傳遞會(huì)非常僵硬——上游返回的字段名和下游接口要求的字段名不一致時(shí)你總不能為了這個(gè)去改接口定義吧。用轉(zhuǎn)換節(jié)點(diǎn)做一層適配問(wèn)題就解決了。第五類(lèi)是子流程節(jié)點(diǎn)。在流程變得復(fù)雜之后你可以把某一段公共邏輯抽成一個(gè)子流程然后在主流程里復(fù)用。這和解代碼時(shí)抽公共函數(shù)是一個(gè)道理。用過(guò)幾次之后你會(huì)發(fā)現(xiàn)編排量和維護(hù)成本都有明顯下降。2.3 為什么推薦“先打通再優(yōu)化”我在做流程設(shè)計(jì)時(shí)有一個(gè)習(xí)慣第一版只求“能跑通”不追求優(yōu)雅和高性能。先把原始數(shù)據(jù)從頭到尾串一遍哪怕有幾處數(shù)據(jù)轉(zhuǎn)換是用腳本硬寫(xiě)的只要結(jié)果正確就行。第二版再去看哪些地方可以?xún)?yōu)化比如兩個(gè)串行接口改并行、公共邏輯抽成子流程、處理失敗的分支再細(xì)分幾類(lèi)異常情況。原因很簡(jiǎn)單流程設(shè)計(jì)和寫(xiě)代碼一樣憑空想十遍不如先落地跑一遍。很多問(wèn)題是在真實(shí)數(shù)據(jù)流經(jīng)過(guò)的時(shí)候才暴露的比如某個(gè)字段在文檔里寫(xiě)的是字符串實(shí)際返回的是數(shù)字這種問(wèn)題你紙上推演根本發(fā)現(xiàn)不了。先跑通建立起一條“能用的基線(xiàn)”再迭代優(yōu)化是成本最低的路徑。還有一點(diǎn)要提醒編排流程上線(xiàn)之后不要急著刪掉中間過(guò)程日志尤其是舊版本。萬(wàn)一新流程出問(wèn)題你還要靠日志對(duì)比定位。平臺(tái)一般會(huì)保留每次執(zhí)行的完整鏈路追蹤這是排查問(wèn)題最關(guān)鍵的依據(jù)比你在代碼里打多少log都直觀。3. 核心配置項(xiàng)詳解與實(shí)操要點(diǎn)3.1 入?yún)⒂成淠玫綌?shù)據(jù)之后的“第一步處理”不管你的觸發(fā)器是HTTP接口還是MQ消息數(shù)據(jù)進(jìn)入流程之后首先要做的是“規(guī)范化”。我見(jiàn)過(guò)很多流程跑不通原因就是把上游字段名字段類(lèi)型直接拿來(lái)用下游接口不認(rèn)。平臺(tái)一般會(huì)提供一個(gè)入?yún)⒍x區(qū)你要在這里聲明流程內(nèi)部的變量把上游數(shù)據(jù)賦值給內(nèi)部變量。這里有個(gè)很重要的細(xì)節(jié)入?yún)⒁?guī)范化的過(guò)程最好單獨(dú)用一個(gè)數(shù)據(jù)轉(zhuǎn)換節(jié)點(diǎn)來(lái)做而不是在各接口節(jié)點(diǎn)里零零散散地改。比如上游傳過(guò)來(lái)的userId是字符串類(lèi)型“10086”但下游用戶(hù)查詢(xún)接口要求的是整型10086你如果在查詢(xún)節(jié)點(diǎn)里寫(xiě)一個(gè)轉(zhuǎn)換腳本那么這個(gè)橋接邏輯就分散在流程各處了。集中處理的好處是以后上游數(shù)據(jù)結(jié)構(gòu)改了你只改一個(gè)地方。實(shí)際操作中我會(huì)把入?yún)⒍x成一套統(tǒng)一命名規(guī)范的變量比如req_order_id、req_user_id后面所有節(jié)點(diǎn)都只引用這套變量不直接引用上游原始字段。這樣做的另一個(gè)好處是排查問(wèn)題時(shí)很清晰——所有輸入都收口在流程開(kāi)頭邏輯鏈路一目了然。3.2 接口調(diào)用節(jié)點(diǎn)的策略配置超時(shí)、重試、降級(jí)接口調(diào)用節(jié)點(diǎn)里有三個(gè)配置是決定流程穩(wěn)定性的核心分別是超時(shí)時(shí)間、重試次數(shù)、失敗處理。很多人一開(kāi)始不在乎直接留默認(rèn)值結(jié)果生產(chǎn)環(huán)境一出問(wèn)題整個(gè)流程被慢接口拖死或者一個(gè)接口反復(fù)重試導(dǎo)致下游壓力翻倍這些都是能避免的。超時(shí)時(shí)間要按接口的實(shí)際耗時(shí)設(shè)置。內(nèi)部接口通常響應(yīng)快300到500毫秒合理第三方接口波動(dòng)大3000到5000毫秒也許才安全。設(shè)置太短稍微慢一點(diǎn)就誤判失敗設(shè)置太長(zhǎng)整個(gè)流程被拖住用戶(hù)感知就是“卡了”。我一般會(huì)先做一次壓測(cè)看該接口的P95耗時(shí)是多少然后在這個(gè)基礎(chǔ)上乘以3到5倍當(dāng)超時(shí)閾值。重試次數(shù)要配合超時(shí)時(shí)間一起想。適合重試的場(chǎng)景是下游接口臨時(shí)性故障比如數(shù)據(jù)庫(kù)連接池滿(mǎn)了、網(wǎng)絡(luò)閃斷不適合重試的場(chǎng)景是下游明確返回業(yè)務(wù)錯(cuò)誤比如參數(shù)校驗(yàn)不通過(guò)、業(yè)務(wù)狀態(tài)不對(duì)。所以重試次數(shù)別盲目設(shè)成3次如果接口返回錯(cuò)誤碼是5xx重試才有意義如果是4xx直接跳失敗分支才是對(duì)的。降級(jí)策略是經(jīng)常被忽略的一項(xiàng)。假設(shè)你的主流程依賴(lài)查詢(xún)一個(gè)商品詳情接口但這個(gè)接口掛了你可以配置成“降級(jí)到緩存數(shù)據(jù)”讓流程不至于整體失敗。平臺(tái)上的實(shí)現(xiàn)方式一般是失敗分支里掛一個(gè)備用節(jié)點(diǎn)或者返回一個(gè)兜底值。這一項(xiàng)配置在核心鏈路上能救命別省。3.3 條件分支與控制邏輯別把流程畫(huà)成蜘蛛網(wǎng)條件判斷節(jié)點(diǎn)在流程設(shè)計(jì)里用多了以后很容易把圖畫(huà)成一張蜘蛛網(wǎng)看的人頭大維護(hù)的人想哭。這里分享幾個(gè)我自己的約束規(guī)則。分支條件數(shù)量盡量控制在3個(gè)以?xún)?nèi)超過(guò)3個(gè)就考慮這個(gè)流程是不是被塞了太多場(chǎng)景該拆分了。舉例來(lái)說(shuō)“根據(jù)訂單類(lèi)型走不同邏輯”和“根據(jù)用戶(hù)類(lèi)型走不同邏輯”如果放在同一個(gè)流程里條件組合就會(huì)膨脹到四個(gè)甚至更多這種流程可讀性極度差。正確的做法是拆成兩個(gè)不同的流程讓各自保持單一職責(zé)。循環(huán)節(jié)點(diǎn)要注意一個(gè)容易踩的坑循環(huán)體內(nèi)調(diào)用接口時(shí)接口返回的數(shù)據(jù)結(jié)構(gòu)可能隨循環(huán)次數(shù)變化或者每次循環(huán)都要重新登錄授權(quán)。遇到這種情況要把授權(quán)獲取放在循環(huán)外循環(huán)內(nèi)只做業(yè)務(wù)調(diào)用。我曾經(jīng)在一個(gè)流程里把token獲取寫(xiě)在循環(huán)內(nèi)部跑2000條數(shù)據(jù)就去調(diào)用2000次認(rèn)證接口下游直接把這個(gè)平臺(tái)IP封了教訓(xùn)非常深刻。并行調(diào)用節(jié)點(diǎn)在畫(huà)出來(lái)之前要先問(wèn)自己一個(gè)問(wèn)題這幾個(gè)接口真的互不依賴(lài)嗎如果下游B接口的入?yún)⑿枰獜腁接口的返回里取那就必須串行。強(qiáng)行并行會(huì)引入數(shù)據(jù)競(jìng)態(tài)最終的運(yùn)行結(jié)果時(shí)對(duì)時(shí)錯(cuò)排查起來(lái)比串行慢得多。4. 完整實(shí)操過(guò)程從零搭一個(gè)訂單通知編排4.1 前置準(zhǔn)備環(huán)境與數(shù)據(jù)約定我用一個(gè)簡(jiǎn)化但不失真實(shí)的場(chǎng)景來(lái)演示訂單系統(tǒng)在用戶(hù)下單成功后調(diào)用接口開(kāi)發(fā)平臺(tái)的編排入口平臺(tái)負(fù)責(zé)依次完成“查詢(xún)用戶(hù)信息”“生成通知內(nèi)容”“發(fā)送短信和站內(nèi)信”三個(gè)動(dòng)作。前置條件三件事第一訂單系統(tǒng)和用戶(hù)系統(tǒng)各提供一個(gè)查詢(xún)接口已經(jīng)發(fā)布到接口平臺(tái)上并完成注冊(cè)第二短信服務(wù)和一個(gè)站內(nèi)信服務(wù)已經(jīng)就緒同樣注冊(cè)到平臺(tái)第三約定好入口HTTP接口的入?yún)⑦@里我定義為orderId訂單號(hào)和userId用戶(hù)ID由訂單系統(tǒng)在用戶(hù)下單成功時(shí)發(fā)起調(diào)用。數(shù)據(jù)約定這一步很多人忽略但它其實(shí)是整個(gè)流程穩(wěn)定的前提。我建議在項(xiàng)目文檔里寫(xiě)清楚每個(gè)外部接口的返回示例以及異常時(shí)的返回結(jié)構(gòu)。后面對(duì)接或者換人維護(hù)時(shí)這份文檔的價(jià)值比流程本身還大。4.2 創(chuàng)建流程搭骨架的詳細(xì)步驟在平臺(tái)的編排列表里新建一個(gè)空流程命名推薦用“場(chǎng)景_動(dòng)作_版本”的格式比如“下單通知_v1”。命名盡量別用“測(cè)試1”“新建流程”這類(lèi)無(wú)法識(shí)別的名字流程多了之后全靠命名找東西。第一步配置HTTP觸發(fā)器指定入口路徑例如/order/notify請(qǐng)求方法用POST請(qǐng)求體格式用JSON。同時(shí)聲明兩個(gè)入?yún)⒆兞縭eq_order_id和req_user_id把請(qǐng)求體里的orderId和userId分別賦給它們。第二步添加用戶(hù)查詢(xún)節(jié)點(diǎn)選擇已注冊(cè)的用戶(hù)查詢(xún)接口。配置入?yún)r(shí)參數(shù)名選擇“用戶(hù)ID”值選擇變量req_user_id。然后配置出參保存比如把接口返回的mobile字段保存到流程變量user_mobile把name保存到user_name。第三步添加生成通知內(nèi)容節(jié)點(diǎn)。這個(gè)節(jié)點(diǎn)用數(shù)據(jù)轉(zhuǎn)換腳本實(shí)現(xiàn)實(shí)際就是把訂單基本信息拼成一段短信文案和一段站內(nèi)信文案。用腳本而不是再做一個(gè)接口是因?yàn)槲谋酒囱b屬于典型的“計(jì)算加工”放在編排層做正合適。腳本里讀取上一步保存的變量輸出notify_sms和notify_inbox兩個(gè)字符串變量。第四步添加發(fā)送短信節(jié)點(diǎn)選擇短信服務(wù)接口入?yún)ⅰ笆謾C(jī)號(hào)”填user_mobile“內(nèi)容”填notify_sms。第五步添加發(fā)送站內(nèi)信節(jié)點(diǎn)入?yún)ⅰ坝脩?hù)ID”填req_user_id“內(nèi)容”填notify_inbox。到這一步骨架就搭完了。保存并發(fā)布到測(cè)試環(huán)境然后發(fā)起一次真實(shí)的HTTP請(qǐng)求來(lái)驗(yàn)證。驗(yàn)證時(shí)我會(huì)注意觀察每一步的執(zhí)行日志平臺(tái)一般會(huì)有流程鏈路追蹤圖每個(gè)節(jié)點(diǎn)的耗時(shí)和出參狀態(tài)直接點(diǎn)開(kāi)就能看到。4.3 加裝異常處理版本升級(jí)的關(guān)鍵一步第一版跑通之后我給這個(gè)流程加了一個(gè)條件判斷節(jié)點(diǎn)判斷“發(fā)送短信”節(jié)點(diǎn)的返回碼。如果返回碼不是成功不直接拋失敗而是進(jìn)入“記錄日志并告警”分支無(wú)論短信是否成功站內(nèi)信照常發(fā)送。為什么這么設(shè)計(jì)因?yàn)槎绦攀〔粦?yīng)該影響站內(nèi)信達(dá)到兩者沒(méi)有強(qiáng)依賴(lài)。如果整個(gè)流程因?yàn)槎绦攀《w失敗訂單系統(tǒng)那邊還得額外處理這個(gè)異常得不償失。配置方法在發(fā)送短信節(jié)點(diǎn)后添加一個(gè)條件判斷節(jié)點(diǎn)條件為“短信發(fā)送狀態(tài)不等于成功”。滿(mǎn)足條件走分支A打日志、發(fā)告警通知到運(yùn)維群不滿(mǎn)足走分支B繼續(xù)往下執(zhí)行站內(nèi)信節(jié)點(diǎn)。同時(shí)把整個(gè)流程的最終響應(yīng)配置成“始終返回成功”——真正的結(jié)果狀態(tài)記錄在流程日志里而不是靠HTTP響應(yīng)碼表達(dá)。這樣做的目的在于讓上游系統(tǒng)保持簡(jiǎn)單。上游只需要知道“編排收到了開(kāi)始處理”最終業(yè)務(wù)成功與否去查日志或回調(diào)即可。這一步的設(shè)計(jì)屬于接口編排經(jīng)驗(yàn)里比較核心的部分編排層要盡可能屏蔽掉細(xì)節(jié)故障而不是把所有異常都拋給調(diào)用方。4.4 測(cè)試用例設(shè)計(jì)別只測(cè)“正常路徑”流程發(fā)布前我的測(cè)試用例一般覆蓋四類(lèi)場(chǎng)景正常數(shù)據(jù)、缺參數(shù)據(jù)、接口超時(shí)、下游返回業(yè)務(wù)錯(cuò)誤。正常數(shù)據(jù)最好準(zhǔn)備三組以上覆蓋不同量級(jí)比如訂單金額一位數(shù)和七位數(shù)、用戶(hù)名字包含特殊字符等。缺參數(shù)據(jù)則是故意少傳userId看流程是否會(huì)按要求直接結(jié)束并在日志里記錄原因。接口超時(shí)場(chǎng)景可以在測(cè)試環(huán)境把下游接口的響應(yīng)時(shí)間調(diào)長(zhǎng)或者直接停用該接口的注冊(cè)地址。下游返回業(yè)務(wù)錯(cuò)誤可以配置一個(gè)測(cè)試專(zhuān)用的返回錯(cuò)誤碼接口來(lái)模擬。這四類(lèi)場(chǎng)景跑完流程在測(cè)試環(huán)境的表現(xiàn)基本就有數(shù)了。還要特別關(guān)注一下流程對(duì)“重復(fù)請(qǐng)求”的處理。如果訂單系統(tǒng)因?yàn)榫W(wǎng)絡(luò)超時(shí)重發(fā)了相同請(qǐng)求你的流程會(huì)不會(huì)創(chuàng)建重復(fù)的通知這個(gè)問(wèn)題不解決上線(xiàn)后最容易出事故。解法一般有兩種一種是在平臺(tái)里配置冪等鍵——相同orderId只允許執(zhí)行一次另一種是流程開(kāi)頭先查一下執(zhí)行記錄重復(fù)就跳過(guò)。設(shè)計(jì)流程時(shí)把這個(gè)考慮進(jìn)去比事后補(bǔ)救便宜得多。5. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄5.1 入?yún)⒌搅说涌趫?bào)“參數(shù)缺失”或“類(lèi)型錯(cuò)誤”這個(gè)問(wèn)題的典型癥狀是流程觸發(fā)成功接口調(diào)用節(jié)點(diǎn)卻報(bào)參數(shù)校驗(yàn)失敗。排查時(shí)先看接口節(jié)點(diǎn)收到的實(shí)際入?yún)⑹鞘裁雌脚_(tái)一般會(huì)記錄請(qǐng)求報(bào)文直接展開(kāi)看字段名和值。最常見(jiàn)的兩個(gè)原因一是變量名映射錯(cuò)了上游保存的字段叫req_user_id接口入?yún)⑴渲美锾畛闪藆ser_id平臺(tái)找不到變量自然報(bào)缺失二是類(lèi)型不一致上游拼出的是字符串“10086”接口要求整型校驗(yàn)失敗。解決辦法也簡(jiǎn)單在接口節(jié)點(diǎn)前加一個(gè)數(shù)據(jù)轉(zhuǎn)換節(jié)點(diǎn)做一層類(lèi)型轉(zhuǎn)換和字段名適配把req_user_id轉(zhuǎn)換成整型并映射為接口期望的userId。我強(qiáng)烈建議所有接口節(jié)點(diǎn)的入?yún)⒍急M量用內(nèi)部統(tǒng)一變量接一層而不是直接引用觸發(fā)器的原始字段這樣出現(xiàn)字段調(diào)整時(shí)你只需要改一個(gè)轉(zhuǎn)換節(jié)點(diǎn)不用挨個(gè)改接口配置。5.2 接口執(zhí)行沒(méi)問(wèn)題但流程整體超時(shí)掛在某一步遇到這種問(wèn)題先看每個(gè)節(jié)點(diǎn)的耗時(shí)分布找到耗時(shí)異常升高的那個(gè)節(jié)點(diǎn)。超時(shí)的原因通常兩類(lèi)。一類(lèi)是下游接口本身變慢比如數(shù)據(jù)庫(kù)慢查詢(xún)、第三方服務(wù)性能波動(dòng)這種情況檢查下游是否有能力問(wèn)題必要時(shí)調(diào)整超時(shí)時(shí)間或增加緩存。另一類(lèi)是接口節(jié)點(diǎn)配置的并發(fā)度或線(xiàn)程池不足多個(gè)流程同時(shí)跑到同一個(gè)節(jié)點(diǎn)時(shí)排隊(duì)等待單個(gè)節(jié)點(diǎn)看起來(lái)沒(méi)慢但整體流程被拉長(zhǎng)。這里有個(gè)我的實(shí)測(cè)心得很多平臺(tái)的接口調(diào)用節(jié)點(diǎn)默認(rèn)并發(fā)數(shù)比較保守比如2或3。如果這個(gè)接口被多個(gè)流程復(fù)用并且本身響應(yīng)時(shí)間正常調(diào)高并發(fā)配置往往立竿見(jiàn)影。但并發(fā)不是越高越好必須評(píng)估下游接口能承受的QPS上限否則你把編排層速度提上去了下游被打掛問(wèn)題又變成5.1了。5.3 流程執(zhí)行失敗但日志沒(méi)有有效錯(cuò)誤信息最讓人頭疼的就是這種“無(wú)聲失敗”。節(jié)點(diǎn)狀態(tài)顯示失敗點(diǎn)開(kāi)詳情日志只有一行“調(diào)用異?!睕](méi)有HTTP狀態(tài)碼也沒(méi)有返回體。這種情況大多數(shù)是下游接口沒(méi)有正確返回JSON格式的響應(yīng)或者接口直接斷連。平臺(tái)解析不了響應(yīng)自然給不出更細(xì)的錯(cuò)誤。排查路徑一般是先手動(dòng)調(diào)一次該接口看返回內(nèi)容是否規(guī)范比如HTTP 200但body為空、或者返回HTML而不是JSON都會(huì)導(dǎo)致平臺(tái)解析失敗。另一個(gè)常見(jiàn)原因是接口的SSL證書(shū)在某些環(huán)境下不受信任導(dǎo)致請(qǐng)求在握手階段就被中斷。這種情況在測(cè)試環(huán)境復(fù)現(xiàn)不了一上生產(chǎn)就隨機(jī)失敗排查時(shí)可以先把節(jié)點(diǎn)配置里的證書(shū)校驗(yàn)臨時(shí)關(guān)掉試一次確認(rèn)問(wèn)題再解決。5.4 變量空間取值混亂串流程了一個(gè)容易被忽視的問(wèn)題同一個(gè)變量名在流程的多個(gè)節(jié)點(diǎn)里被重復(fù)賦值后續(xù)節(jié)點(diǎn)取到的值和預(yù)期不符。尤其是用腳本節(jié)點(diǎn)時(shí)腳本里順手寫(xiě)了一個(gè)變量賦值名字和前面某個(gè)節(jié)點(diǎn)保存的變量重名就會(huì)把原來(lái)的值覆蓋掉。排查思路從目標(biāo)節(jié)點(diǎn)倒序回看所有寫(xiě)操作確認(rèn)這個(gè)變量名最后一次被賦值是在哪里。更推薦的辦法是建立命名約定觸發(fā)器產(chǎn)生的原始數(shù)據(jù)用req_前綴中間轉(zhuǎn)換結(jié)果用tmp_前綴最終的對(duì)外輸出用out_前綴。別嫌麻煩流程一復(fù)雜這個(gè)習(xí)慣能幫你省大量排錯(cuò)時(shí)間。5.5 常見(jiàn)問(wèn)題速查表問(wèn)題現(xiàn)象可能原因優(yōu)先排查動(dòng)作接口報(bào)參數(shù)缺失變量名映射錯(cuò)誤查看請(qǐng)求報(bào)文中的實(shí)際入?yún)⒔涌趫?bào)類(lèi)型錯(cuò)誤字符串與整型不匹配在數(shù)據(jù)轉(zhuǎn)換節(jié)點(diǎn)強(qiáng)制轉(zhuǎn)型流程整體超時(shí)單節(jié)點(diǎn)耗時(shí)過(guò)大或并發(fā)太低查看各節(jié)點(diǎn)耗時(shí)分布執(zhí)行失敗但無(wú)錯(cuò)誤信息下游返回非JSON或斷連手動(dòng)請(qǐng)求該接口檢查返回變量值不確定變量名被重復(fù)賦值反向追蹤所有寫(xiě)操作通知類(lèi)消息重復(fù)發(fā)送缺少冪等控制配置冪等鍵或檢查記錄測(cè)試正常生產(chǎn)偶發(fā)失敗證書(shū)或網(wǎng)絡(luò)環(huán)境差異關(guān)閉證書(shū)校驗(yàn)試一次6. 我的一點(diǎn)心得體會(huì)接口編排用得久了我越來(lái)越覺(jué)得它本質(zhì)上是“把分布式系統(tǒng)之間的協(xié)作關(guān)系顯性化”。以前這些調(diào)用關(guān)系藏在代碼里只有開(kāi)發(fā)自己心里清楚別人接手就得翻代碼、猜邏輯。編排化之后整條鏈路攤開(kāi)在畫(huà)布上誰(shuí)都能看懂評(píng)審、交接、排查的門(mén)檻全都降低了。實(shí)際落地的時(shí)候還有幾個(gè)小建議順手分享出來(lái)。第一編排流程的版本管理務(wù)必重視發(fā)布新版本前至少留一個(gè)穩(wěn)定版本在線(xiàn)方便隨時(shí)回退。平臺(tái)一般支持一鍵回退但你要有意識(shí)地用別等出事了才臨時(shí)翻文檔。第二給流程加一個(gè)匯總性的業(yè)務(wù)監(jiān)控視圖比如創(chuàng)建一個(gè)統(tǒng)計(jì)流程“每天執(zhí)行多少次、成功多少、失敗多少”的子流程定時(shí)跑結(jié)果推送給團(tuán)隊(duì)群。這個(gè)投入很小但長(zhǎng)期價(jià)值非常大。第三編排流程也要做定期巡檢就像代碼需要重構(gòu)一樣流程跑半年后回過(guò)頭看看有沒(méi)有冗余節(jié)點(diǎn)、有沒(méi)有可以抽成子流程的地方該優(yōu)化的就優(yōu)化。我在最初做接口編排的時(shí)候也經(jīng)歷過(guò)“什么節(jié)點(diǎn)都想往流程里塞”的階段結(jié)果流程又大又脆。后來(lái)踩過(guò)幾次坑慢慢學(xué)會(huì)一個(gè)原則編排層輕一點(diǎn)邏輯清晰一點(diǎn)把每一個(gè)節(jié)點(diǎn)都當(dāng)成獨(dú)立的服務(wù)來(lái)對(duì)待?,F(xiàn)在我再搭流程能拆成兩步的就絕不合成一步能用子流程復(fù)用的就絕不復(fù)制粘貼節(jié)點(diǎn)。這個(gè)內(nèi)容后續(xù)還可以往兩個(gè)方向擴(kuò)展一是做更細(xì)致的權(quán)限設(shè)計(jì)比如不同角色只能編輯自己負(fù)責(zé)的那部分流程防止誤操作影響全局二是把編排與監(jiān)控告警打通得更徹底做到流程一抖動(dòng)告警自動(dòng)帶著鏈路信息發(fā)到責(zé)任人手里。如果你手頭也正在用接口編排處理復(fù)雜的集成需求希望這篇分享能幫你少走一些彎路。