接實(shí)戰(zhàn):字段、異常與性能優(yōu)化全指南)
做電商數(shù)據(jù)對(duì)接這幾年我接得最多的一個(gè)接口就是1688 商品詳情 API。乍看它就是個(gè)單商品查詢(xún)接口傳一個(gè)商品 ID返回一堆商品字段比訂單、物流那套邏輯簡(jiǎn)單得多。可真要把它用在商品同步、比價(jià)、選品、商品上架這類(lèi)業(yè)務(wù)里坑是一個(gè)接一個(gè)價(jià)格字段在不同商品上的含義不一樣庫(kù)存有時(shí)只是展示值文檔里的字段和線上返回經(jīng)常對(duì)不上QPS 一高還會(huì)莫名其妙報(bào)錯(cuò)。這篇文章的內(nèi)容主要圍繞三塊展開(kāi)商品詳情字段到底怎么用、異常情況怎么排查、在高頻調(diào)用下怎么把性能做穩(wěn)定。不管你是第一次對(duì)接的新手還是已經(jīng)在維護(hù)同步任務(wù)的老手后續(xù)內(nèi)容都是可以從實(shí)際項(xiàng)目里直接拿走的經(jīng)驗(yàn)。1. 接入前的三個(gè)決定權(quán)限邊界、數(shù)據(jù)用途、接口版本很多團(tuán)隊(duì)把這個(gè)接口想得太簡(jiǎn)單拿到文檔就開(kāi)始寫(xiě)代碼。我自己的經(jīng)驗(yàn)是接入前的三個(gè)決定沒(méi)做好后面所有環(huán)節(jié)都是在給前面補(bǔ)窟窿你拿商品詳情來(lái)干什么、你手上有多少調(diào)用配額、你鎖定的接口版本是什么。1.1 先回答“拿到詳情要干什么”再?zèng)Q定字段清單商品詳情 API 不是一個(gè)固定的返回結(jié)構(gòu)。同一個(gè)商品 ID不同業(yè)務(wù)場(chǎng)景下關(guān)心的字段完全不同。比如做商品刊登/鋪貨需要完整標(biāo)題、主圖、SKU 規(guī)格、詳情描述、類(lèi)目屬性字段越全越好。做價(jià)格監(jiān)控只需要商品 ID、SKU 級(jí)價(jià)格、庫(kù)存數(shù)量、標(biāo)題其他大字段完全沒(méi)必要拉。做選品分析銷(xiāo)量、評(píng)價(jià)數(shù)、店鋪信息、收藏?cái)?shù)比描述字段更有價(jià)值。我見(jiàn)過(guò)不少團(tuán)隊(duì)在第一次對(duì)接時(shí)直接把整個(gè)返回體 JSON 原封不動(dòng)落庫(kù)理由是“先存著以后可能用到”。這個(gè)做法有兩個(gè)問(wèn)題第一1688 商品詳情里有些字段非常大比如詳情描述里的圖片列表一次調(diào)用可能占幾十 KB全量存下去數(shù)據(jù)庫(kù)膨脹得很快第二接口配額是有限的你為不需要的字段多消耗的流量和解析時(shí)間最后都會(huì)變成性能瓶頸。所以接入前第一件事是把業(yè)務(wù)需要的字段列成清單和接口文檔比對(duì)盡量按需拉取或按需落庫(kù)。如果接口不支持選擇字段那就在消費(fèi)端做裁剪進(jìn)入消息隊(duì)列或數(shù)據(jù)庫(kù)之前就把大字段拆開(kāi)處理。1.2 權(quán)限、環(huán)境和版本號(hào)需要提前摸清的隱性條件商品詳情 API 的權(quán)限體系通常涉及幾層應(yīng)用憑證App Key / Secret、接口權(quán)限包、IP 白名單、QPS 配額。這幾個(gè)東西經(jīng)常會(huì)成為線上事故的源頭。先說(shuō) QPS 配額。這個(gè)配額通常不是“單接口無(wú)限調(diào)”而是平臺(tái)按照應(yīng)用維度分配的。你申請(qǐng)到的可能是每秒 20 次也可能是每秒 200 次具體看賬號(hào)等級(jí)和接口包。很多人在本地測(cè)試時(shí)感覺(jué)不到限制一上線批量任務(wù)就撞上限流。上線前務(wù)必搞清楚配額數(shù)字并且把配額作為配置項(xiàng)寫(xiě)進(jìn)代碼里而不是靠人腦記。再說(shuō)接口版本。1688 商品詳情 API 的字段結(jié)構(gòu)在不同版本里是有差異的。比如老的返回結(jié)構(gòu)里價(jià)格字段可能叫price新版本可能改成了price_range或者sku_price。我踩過(guò)的一次坑是文檔寫(xiě)著 V2 版本線上實(shí)際請(qǐng)求路徑還是 V1返回字段缺了一半解析程序直接報(bào)空指針。建議在代碼里顯式聲明版本號(hào)并在日志里打印每次請(qǐng)求的版本參數(shù)這樣出問(wèn)題的時(shí)候能快速判斷是不是版本切換引起的。還有一個(gè)容易被忽略的點(diǎn)授權(quán)有效期。很多憑證不是永久有效的有的可能 24 小時(shí)有的是長(zhǎng)期 token有的過(guò)期后需要重新走授權(quán)流程。如果你在代碼里把 token 寫(xiě)死又沒(méi)做過(guò)期監(jiān)控某天凌晨同步任務(wù)會(huì)突然大量失敗。所以接入前就要設(shè)計(jì)好憑證過(guò)期告警以及自動(dòng)刷新邏輯。2. 商品詳情字段拆解哪些能直接落庫(kù)哪些要二次加工商品詳情 API 的返回體里字段可以分為幾類(lèi)基礎(chǔ)信息字段、價(jià)格庫(kù)存字段、文本描述字段、圖片與媒體字段、賣(mài)家與店鋪?zhàn)侄巍O旅嫖野磳?shí)際使用頻率和踩坑概率逐塊講。2.1 基礎(chǔ)字段映射與建表建議最常見(jiàn)的幾個(gè)基礎(chǔ)字段我整理了一張參考表字段名可能因?yàn)榻涌诎姹疚⒄{(diào)但業(yè)務(wù)含義基本一致字段業(yè)務(wù)含義類(lèi)型示例落庫(kù)建議商品 ID平臺(tái)商品唯一標(biāo)識(shí)所有后續(xù)關(guān)聯(lián)的主鍵字符串/數(shù)字唯一索引字符串類(lèi)型存儲(chǔ)避免精度丟失標(biāo)題商品標(biāo)題用于展示和搜索字符串原樣存儲(chǔ)可另存一個(gè)清洗后的檢索字段主圖商品主圖 URL可直接外鏈或轉(zhuǎn)存字符串建議轉(zhuǎn)存到自己 CDN防外鏈?zhǔn)ь?lèi)目 ID商品所在類(lèi)目數(shù)字關(guān)聯(lián)類(lèi)目表類(lèi)目可能變化需記錄歷史屬性商品關(guān)鍵屬性 KV 集合JSON/數(shù)組拆成屬性表或 JSON 字段按檢索需求取舍創(chuàng)建時(shí)間/更新時(shí)間可用于增量同步時(shí)間戳必建索引增量同步的主要依賴(lài)建表的時(shí)候有一個(gè)值得注意的點(diǎn)商品 ID 別用數(shù)據(jù)庫(kù)自增主鍵替代。1688 的商品 ID 本身是全局唯一的直接用它做業(yè)務(wù)主鍵最穩(wěn)妥。如果你用自增 ID 做內(nèi)部主鍵外部系統(tǒng)回傳數(shù)據(jù)時(shí)還要維護(hù)映射關(guān)系純粹給自己增加復(fù)雜度。標(biāo)題和屬性這類(lèi)字段我建議落庫(kù)時(shí)保留原始值同時(shí)生成一個(gè)“標(biāo)準(zhǔn)化副本”。比如標(biāo)題里有大量營(yíng)銷(xiāo)詞語(yǔ)排序時(shí)可能干擾匹配“標(biāo)準(zhǔn)化副本”可以去掉符號(hào)、停用詞用于搜索和去重。這個(gè)副本不用實(shí)時(shí)更新每天做一次清洗就行。2.2 價(jià)格和庫(kù)存字段最容易出幺蛾子價(jià)格字段是商品詳情 API 里最容易讓人誤判的地方。常見(jiàn)的價(jià)格返回有幾種形態(tài)單價(jià)格一口價(jià)商品直接返回一個(gè)價(jià)格值。區(qū)間價(jià)格多 SKU 商品返回最低價(jià)和最高價(jià)比如price_range。SKU 價(jià)格每個(gè) SKU 自己的價(jià)格藏在sku_list或skus結(jié)構(gòu)里。促銷(xiāo)價(jià)格部分商品有活動(dòng)價(jià)、優(yōu)惠券價(jià)和原價(jià)是分開(kāi)的字段。如果你只看頂層價(jià)格很容易出現(xiàn)“頁(yè)面顯示 9.9 元起你拿到的卻是另一個(gè)價(jià)格”的現(xiàn)象。處理價(jià)格字段的第一原則是以 SKU 維度為準(zhǔn)不要以商品維度為準(zhǔn)。商品維度價(jià)格只適合做列表頁(yè)展示不適合做價(jià)格判斷。庫(kù)存同樣要小心。有些商品的庫(kù)存字段是平臺(tái)展示用的“虛擬庫(kù)存”和真實(shí)可拍庫(kù)存不一定一致。你把一個(gè)商品同步到自己店鋪客戶(hù)下單成功了結(jié)果上游那邊實(shí)際沒(méi)貨后續(xù)履約就會(huì)出問(wèn)題。所以庫(kù)存字段建議作為參考值不要作為可售庫(kù)存的唯一依據(jù)。如果業(yè)務(wù)強(qiáng)依賴(lài)庫(kù)存需要額外對(duì)接庫(kù)存查詢(xún)或下單校驗(yàn)接口。價(jià)格和庫(kù)存這類(lèi)變動(dòng)頻繁的數(shù)據(jù)我建議單獨(dú)建一張“商品快照表”每次同步都插入一條帶時(shí)間戳的記錄。這樣既能追溯價(jià)格變化也能在后續(xù)做價(jià)格監(jiān)控時(shí)直接基于這張表出報(bào)表不用再天天調(diào)接口。表結(jié)構(gòu)大致是商品 ID、SKU ID、價(jià)格、庫(kù)存、抓取時(shí)間、接口響應(yīng)耗時(shí)。這張表的數(shù)據(jù)量會(huì)漲很快需要做分區(qū)或定期歸檔。2.3 描述、屬性這類(lèi)文本字段必須做清洗再入庫(kù)商品詳情描述字段一般是個(gè)大 HTML 塊里面包含很多圖片鏈接、樣式標(biāo)簽甚至可能有腳本片段。直接把它存進(jìn)數(shù)據(jù)庫(kù)再渲染到自己的頁(yè)面會(huì)有兩個(gè)問(wèn)題第一體積太大一個(gè)詳情描述幾百 KB 很正常第二安全風(fēng)險(xiǎn)第三方內(nèi)容里的腳本如果沒(méi)過(guò)濾等于在自己頁(yè)面開(kāi)了一個(gè)口子。我的處理方法是分三步提取詳情里的圖片 URL 列表單獨(dú)存一張?jiān)斍閳D片表。用白名單策略過(guò)濾 HTML 標(biāo)簽和屬性只保留安全的排版標(biāo)簽。把圖片地址統(tǒng)一替換成自己 CDN 上的轉(zhuǎn)存地址避免外鏈?zhǔn)Щ蛲下?yè)面加載。屬性字段同樣需要處理。1688 商品屬性有些是標(biāo)準(zhǔn)屬性有些是賣(mài)家自定義屬性。標(biāo)準(zhǔn)屬性可以直接映射成規(guī)范字段自定義屬性往往是鍵值對(duì)形式建議拆成attr_name, attr_value兩張表或者 JSON 字段搜索時(shí)再做展開(kāi)。不要把所有屬性拼成一個(gè)字符串存起來(lái)否則后面做屬性篩選會(huì)非常痛苦。3. 異常處理不能只靠 try catch一條能落地的排查鏈路商品詳情 API 接入后線上一定會(huì)有各種異常。如果代碼里只是給每個(gè)調(diào)用套一個(gè) try catch失敗了打一條日志就完事那問(wèn)題出現(xiàn)時(shí)你只能對(duì)著日志發(fā)愁。異常處理要有一套完整的鏈路識(shí)別錯(cuò)誤類(lèi)型、記錄現(xiàn)場(chǎng)信息、按策略重試、最終降級(jí)。3.1 常見(jiàn)錯(cuò)誤碼與處理策略速查表下面這些是我在對(duì)接過(guò)程中真實(shí)遇到過(guò)的異常類(lèi)別整理成表方便排查異?,F(xiàn)象/錯(cuò)誤碼常見(jiàn)原因處理策略參數(shù)錯(cuò)誤商品 ID 格式不對(duì)、缺少必填字段記錄日志不重試檢查入?yún){證無(wú)效token 過(guò)期、App Key 被禁用觸發(fā)重新授權(quán)告警通知管理員IP 不在白名單服務(wù)器出口 IP 變更更新白名單配置頻率超限超出 QPS 配額指數(shù)退避重試降低并發(fā)商品不存在/已下架商品被刪除、類(lèi)目調(diào)整或接口同步延遲標(biāo)記商品狀態(tài)進(jìn)入待確認(rèn)隊(duì)列系統(tǒng)繁忙平臺(tái)側(cè)抖動(dòng)或局部限流延遲重試配合熔斷請(qǐng)求超時(shí)網(wǎng)絡(luò)問(wèn)題或平臺(tái)響應(yīng)慢重試并記錄響應(yīng)耗時(shí)看到這里你可能發(fā)現(xiàn)不是所有異常都該重試。參數(shù)錯(cuò)誤和憑證無(wú)效這類(lèi)問(wèn)題重試一萬(wàn)次都不會(huì)成功只會(huì)白白消耗配額和增加日志量。只有超時(shí)、限流、系統(tǒng)繁忙這類(lèi)瞬時(shí)異常才適合重試。3.2 一次線上批量同步失敗的實(shí)際排查順序說(shuō)一個(gè)我實(shí)際經(jīng)歷過(guò)的場(chǎng)景。某天凌晨一個(gè)定時(shí)同步任務(wù)突然大量報(bào)錯(cuò)錯(cuò)誤碼是“系統(tǒng)繁忙”。第一反應(yīng)是平臺(tái)側(cè)出問(wèn)題了但仔細(xì)看發(fā)現(xiàn)報(bào)錯(cuò)時(shí)間段非常集中而且同一個(gè)商品 ID 在重試幾次后又成功了。當(dāng)時(shí)我是按這個(gè)順序排查的看錯(cuò)誤碼分布把日志按錯(cuò)誤碼聚合成表格確認(rèn)是不是單一錯(cuò)誤碼還是有幾種錯(cuò)誤碼混合??磿r(shí)間分布把失敗請(qǐng)求按分鐘維度統(tǒng)計(jì)看有沒(méi)有明顯的波峰。結(jié)果發(fā)現(xiàn)失敗集中在某個(gè)分鐘的整點(diǎn)前后。看自己的調(diào)用量那段時(shí)間正好加了一個(gè)新的全量校驗(yàn)任務(wù)和原有的增量同步任務(wù)疊加實(shí)際并發(fā)量已經(jīng)超過(guò)配額。看單次響應(yīng)耗時(shí)拉出成功請(qǐng)求的耗時(shí)曲線發(fā)現(xiàn)耗時(shí)在慢慢上漲說(shuō)明是本地堆積導(dǎo)致超時(shí)而不是上游全掛。單飛驗(yàn)證手動(dòng)調(diào)一個(gè)商品 ID立刻成功。確認(rèn)問(wèn)題不在商品本身。最后結(jié)論是兩個(gè)任務(wù)共用同一個(gè)應(yīng)用憑證總 QPS 超過(guò)了配額被平臺(tái)限流。處理方式也很簡(jiǎn)單把任務(wù)錯(cuò)峰運(yùn)行并為不同任務(wù)拆分配額。這次排查給我的教訓(xùn)是遇到異常不要先懷疑平臺(tái)先在自己的調(diào)用日志里找證據(jù)。3.3 重試、冪等與降級(jí)三件事缺一不可先看重試。重試不是“失敗了就再來(lái)一次”而是要有節(jié)奏。固定間隔重試容易在恢復(fù)瞬間造成請(qǐng)求風(fēng)暴指數(shù)退避加隨機(jī)抖動(dòng)是更穩(wěn)的方案。比如第一次失敗等 1 秒第二次等 2 秒第三次等 4 秒每次加一點(diǎn)隨機(jī)值避免多個(gè)任務(wù)同步重試時(shí)再次撞車(chē)。import random import time def fetch_with_retry(product_id, max_retries3): delay 1 for attempt in range(max_retries): try: return api.fetch_product_detail(product_id) except ApiRateLimitError: time.sleep(delay random.uniform(0, 0.5)) delay * 2 except ApiTimeoutError: time.sleep(delay random.uniform(0, 0.5)) delay * 2 raise FetchDetailFailed(product_id)然后是冪等。商品詳情數(shù)據(jù)落到數(shù)據(jù)庫(kù)時(shí)要保證同一批數(shù)據(jù)重復(fù)執(zhí)行不會(huì)產(chǎn)生臟數(shù)據(jù)。最簡(jiǎn)單的做法是給每次同步任務(wù)生成一個(gè)任務(wù) ID數(shù)據(jù)表里以“商品 ID 任務(wù) ID 抓取時(shí)間”作為唯一鍵重復(fù)拉取時(shí)直接覆蓋或跳過(guò)。這能防止重試帶來(lái)的重復(fù)插入。最后是降級(jí)。接口不可用時(shí)不能讓核心流程完全斷掉。我的常規(guī)做法是讀數(shù)據(jù)時(shí)先讀本地緩存緩存沒(méi)有才調(diào)接口接口異常時(shí)返回上一次成功的快照數(shù)據(jù)同時(shí)把失敗任務(wù)放進(jìn)延遲隊(duì)列等服務(wù)恢復(fù)后再補(bǔ)拉。對(duì)用戶(hù)來(lái)說(shuō)看到的商品頁(yè)面不會(huì)空白只是數(shù)據(jù)可能不是最新的這在很多業(yè)務(wù)場(chǎng)景里是可以接受的。4. 性能優(yōu)化在限流與延遲之間找到平衡點(diǎn)商品詳情 API 的性能優(yōu)化核心不是把單次調(diào)用做到多快而是在有限的 QPS 配額下讓整體數(shù)據(jù)同步既穩(wěn)又新。這塊我分成四個(gè)部分講。4.1 先用小規(guī)模壓測(cè)摸清接口的真實(shí)底線很多人一上來(lái)就寫(xiě) 20 個(gè)線程并發(fā)拉取想著速度快一點(diǎn)。結(jié)果不是被限流就是把平臺(tái)接口搞得響應(yīng)變慢最后速度反而更慢。正確做法是先做小規(guī)模壓測(cè)。我的建議是用一個(gè)小任務(wù)比如 100 個(gè)商品 ID分別用 1 并發(fā)、2 并發(fā)、5 并發(fā)跑一遍記錄每次的 QPS、平均耗時(shí)、P95 耗時(shí)、失敗率。這樣可以確定幾個(gè)關(guān)鍵數(shù)據(jù)接口的穩(wěn)定 QPS 大概是多少。單次調(diào)用的正常耗時(shí)范圍。并發(fā)升高到多少時(shí)失敗率開(kāi)始明顯上漲。有了這些數(shù)據(jù)你就能給線上任務(wù)設(shè)置一個(gè)合理的“安全并發(fā)數(shù)”一般建議留 20% 到 30% 余量。比如壓測(cè)發(fā)現(xiàn) 5 并發(fā)時(shí) QPS 能到 8那線上就設(shè)置 4 并發(fā)避免偶發(fā)波動(dòng)直接撞上限流。4.2 緩存分層熱點(diǎn)數(shù)據(jù)與易變數(shù)據(jù)分開(kāi)管商品詳情接口的特點(diǎn)是熱點(diǎn)商品會(huì)被頻繁請(qǐng)求但核心字段變化頻率并不高。如果不做緩存同一款爆品的詳情一天被拉幾千次純屬浪費(fèi)配額。我的緩存策略分兩層本地緩存進(jìn)程內(nèi)適合放標(biāo)題、主圖、屬性這類(lèi)幾乎不變的數(shù)據(jù)TTL 可以設(shè)置得比較長(zhǎng)比如 1 小時(shí)。分布式緩存比如 Redis適合放價(jià)格、庫(kù)存這類(lèi)變化相對(duì)頻繁的數(shù)據(jù)TTL 建議 5 到 15 分鐘具體看業(yè)務(wù)對(duì)時(shí)效性的要求。緩存更新的方式建議用“讀取時(shí)回源 主動(dòng)失效”請(qǐng)求進(jìn)來(lái)先查緩存緩存沒(méi)有或過(guò)期再調(diào)接口調(diào)完回填緩存。當(dāng)后臺(tái)同步任務(wù)發(fā)現(xiàn)商品價(jià)格變更時(shí)主動(dòng)刪除對(duì)應(yīng)緩存 key讓下一次請(qǐng)求重新拉取。另外三個(gè)經(jīng)典問(wèn)題也要預(yù)防緩存穿透一個(gè)不存在的商品 ID 反復(fù)查詢(xún)每次都打到接口。解決方法是緩存空值或者用布隆過(guò)濾器攔截明顯不存在的 ID。緩存擊穿某個(gè)熱點(diǎn) key 過(guò)期瞬間大量請(qǐng)求同時(shí)回源。解決方法是加互斥鎖只允許一個(gè)請(qǐng)求回源其余請(qǐng)求等緩存刷新。緩存雪崩大面積 key 同時(shí)過(guò)期。解決方法是給 TTL 加隨機(jī)抖動(dòng)比如基礎(chǔ) 300 秒加 0 到 60 秒隨機(jī)值。4.3 并發(fā)控制與批量任務(wù)的平滑限速并發(fā)控制這塊我推薦用信號(hào)量Semaphore限制同時(shí)進(jìn)行的請(qǐng)求數(shù)而不是直接裸上線程池。信號(hào)量的好處是即使任務(wù)來(lái)源有多個(gè)也能統(tǒng)一控制入口。import threading import time semaphore threading.Semaphore(4) # 最多 4 個(gè)并發(fā)請(qǐng)求 def fetch_with_limit(product_id): with semaphore: return api.fetch_product_detail(product_id)批量任務(wù)還需要考慮“平滑限速”。比如配額是每秒 10 次你不能在前 0.5 秒把 10 次全打出去那樣流量會(huì)是脈沖式的。建議用令牌桶思路每秒均勻放行一定數(shù)量的請(qǐng)求或者把 N 個(gè)請(qǐng)求平均分布到 1 秒的時(shí)間窗口里。還有一個(gè)實(shí)際經(jīng)驗(yàn)不同任務(wù)要錯(cuò)峰。全量同步、增量同步、人工刷新這三個(gè)任務(wù)如果同時(shí)跑很容易把配額打滿(mǎn)。我通常會(huì)在配置中心里給每個(gè)任務(wù)設(shè)置一個(gè)時(shí)間窗口比如整點(diǎn)只允許增量任務(wù)跑半點(diǎn)允許全量任務(wù)跑人工刷新走獨(dú)立配額。4.4 字段裁剪與圖片資源處理進(jìn)一步降低開(kāi)銷(xiāo)商品詳情接口返回的 JSON 有可能非常大尤其詳情描述和多圖字段。如果你把整包響應(yīng)體都傳到下游解析時(shí)間和網(wǎng)絡(luò)開(kāi)銷(xiāo)都會(huì)成倍增長(zhǎng)。我的做法是在消費(fèi)端做字段裁剪只保留業(yè)務(wù)需要的字段組裝成精簡(jiǎn)結(jié)構(gòu)再落庫(kù)或進(jìn)緩存。圖片這塊詳情描述里頻繁出現(xiàn)的原圖 URL 通常體積很大。如果業(yè)務(wù)只是用來(lái)展示商品詳情頁(yè)可以替換成帶縮略圖參數(shù)的 CDN 地址既省流量又提升加載速度。主圖也是一樣列表場(chǎng)景用縮略圖詳情頁(yè)再用原圖。網(wǎng)絡(luò)層也別忽視開(kāi)啟 HTTP 連接池復(fù)用避免每次請(qǐng)求都新建連接確認(rèn)接口支持 gzip 壓縮響應(yīng)能省不少帶寬超時(shí)時(shí)間不要設(shè)置得過(guò)長(zhǎng)一般連接超時(shí) 3 秒、讀取超時(shí) 10 秒就夠避免線程被慢請(qǐng)求長(zhǎng)期占用。5. 穩(wěn)定性兜底增量同步與對(duì)賬機(jī)制不可省前面把字段和異常都理清了性能也調(diào)優(yōu)了但還有一個(gè)問(wèn)題平臺(tái)數(shù)據(jù)隨時(shí)在變?nèi)绾伪WC自己庫(kù)里的數(shù)據(jù)始終接近真實(shí)這就要靠增量同步和對(duì)賬機(jī)制兜底。5.1 用更新時(shí)間輪詢(xún)替代全量拉取商品詳情接口如果返回了商品的更新時(shí)間字段優(yōu)先基于這個(gè)字段做增量同步。維護(hù)一張“同步游標(biāo)”表記錄每個(gè)類(lèi)目或每個(gè)任務(wù)上次抓取到的時(shí)間點(diǎn)下次只拉取更新商品。這個(gè)策略能大幅減少調(diào)用量把配額用在真正變化的數(shù)據(jù)上。但要注意更新時(shí)間字段不一定可靠。有的接口只在整點(diǎn)刷新有的商品明明改了價(jià)格但更新時(shí)間沒(méi)變。所以不能完全依賴(lài)它還需要定期做一次全量對(duì)賬比如每天凌晨跑一遍全量比對(duì)把漏掉的更新補(bǔ)回來(lái)。如果接口沒(méi)有返回更新時(shí)間字段那增量就不好做。這種情況下可以用“分片全量?jī)?yōu)先級(jí)刷新”的思路把商品分成多個(gè)分片每天輪流全量刷新一部分同時(shí)對(duì)高熱度商品單獨(dú)設(shè)置更短的刷新周期。這個(gè)方案雖然沒(méi)有純?cè)隽啃矢叩珓僭趯?shí)現(xiàn)簡(jiǎn)單、不會(huì)漏數(shù)據(jù)。5.2 對(duì)賬任務(wù)與臟數(shù)據(jù)補(bǔ)償是線上經(jīng)驗(yàn)的最后一層保障對(duì)賬任務(wù)的目標(biāo)是發(fā)現(xiàn)并修正數(shù)據(jù)差異。我會(huì)維護(hù)一張對(duì)賬表記錄每個(gè)商品的上次同步時(shí)間、本次同步時(shí)間、數(shù)據(jù)是否一致、差異字段。對(duì)賬分兩個(gè)層面完整性對(duì)賬本地有記錄的商品上游是否還存在上游存在的商品本地是否漏采。一致性對(duì)賬標(biāo)題、價(jià)格、庫(kù)存、主圖這些關(guān)鍵字段是否一致價(jià)格差多少才判定為異常。發(fā)現(xiàn)差異后需要有一個(gè)補(bǔ)償機(jī)制。我常用的做法是差異商品進(jìn)入一張“待補(bǔ)償隊(duì)列”按優(yōu)先級(jí)排序。價(jià)格差異的優(yōu)先處理因?yàn)橹苯佑绊懡灰讟?biāo)題圖片差異可以放到低峰期批量處理。補(bǔ)償任務(wù)同樣要遵守 QPS 配額不能因?yàn)檠a(bǔ)數(shù)據(jù)又把接口打掛。對(duì)賬任務(wù)建議單獨(dú)用一個(gè)應(yīng)用憑證或單獨(dú)的配額避免對(duì)正常業(yè)務(wù)造成影響。以前我就吃過(guò)這個(gè)虧對(duì)賬任務(wù)和線上同步共用配額結(jié)果對(duì)賬一跑線上商品詳情刷新直接失敗用戶(hù)看到的還是舊數(shù)據(jù)。另外日志和監(jiān)控一定不能省。商品詳情 API 的調(diào)用日志建議包含這些字段請(qǐng)求時(shí)間、商品 ID、接口版本、響應(yīng)耗時(shí)、響應(yīng)碼、錯(cuò)誤信息、使用哪個(gè)任務(wù)調(diào)用。有了這些信息線上出問(wèn)題時(shí)才能快速定位是上游問(wèn)題、本地并發(fā)問(wèn)題還是數(shù)據(jù)加工問(wèn)題。監(jiān)控告警閾值可以這樣設(shè)錯(cuò)誤率超過(guò) 5% 告警、P95 耗時(shí)超過(guò) 3 秒告警、單任務(wù)連續(xù)失敗超過(guò) 5 次告警。最后再分享一個(gè)小技巧詳情描述里的圖片鏈接我一般會(huì)在入庫(kù)時(shí)統(tǒng)一換成自己的 CDN 地址同時(shí)把原始鏈接留一個(gè)備份字段。這樣即使 CDN 臨時(shí)故障也能用備份鏈接快速恢復(fù)展示不至于讓商品詳情頁(yè)變成一片空白。這個(gè)細(xì)節(jié)看著小在真正的高流量場(chǎng)景里能省下很多麻煩。