微信二次開發(fā):如何利用clientMsgId做好消息冪等)
昨晚快零點(diǎn)的時(shí)候一個(gè)做社群發(fā)售的大客戶群直接炸鍋了“你們這機(jī)器人是不是瘋了客戶群里問(wèn)了一句優(yōu)惠券怎么領(lǐng)機(jī)器人一口氣連發(fā)了 5 遍一樣的回復(fù)瞬間把群刷屏了好幾個(gè)大R客戶嫌煩直接退群了這損失算誰(shuí)的”我趕緊連上他們的服務(wù)器看日志果不其然完全沒(méi)做消息去重。一遇到網(wǎng)絡(luò)抖動(dòng)底層網(wǎng)關(guān)觸發(fā)重試這幫兄弟的代碼就跟個(gè)傻子一樣來(lái)一次請(qǐng)求算一次答案硬生生把自己變成了“復(fù)讀機(jī)”。作為一名每天在一線高頻處理微信及企微 API 接口機(jī)器人客戶問(wèn)題的銷售客服看到這種因?yàn)闆](méi)搞懂“消息冪等”而造成的運(yùn)營(yíng)車禍我真是一頭霧水又替他們捉急。很多研發(fā)眼里只有“收”和“發(fā)”根本沒(méi)有防重試的底層思維。今天咱們別扯虛的直接基于星云API xingyapi.com的底層通信架構(gòu)帶你死磕“消息冪等”這個(gè)硬核邏輯。把clientMsgId怎么用徹底盤明白讓你的機(jī)器人再也不犯抽。什么是冪等為什么你的機(jī)器人會(huì)變“復(fù)讀機(jī)”企微的底層網(wǎng)關(guān)有一個(gè)死鐵律不管是給你推 Webhook還是你調(diào)接口發(fā)消息只要 5 秒內(nèi)沒(méi)收到明確的 HTTP 200 成功響應(yīng)網(wǎng)關(guān)就會(huì)認(rèn)為網(wǎng)絡(luò)斷了然后立刻發(fā)起瘋狂重試。如果你的代碼處理得慢比如查庫(kù)慢、調(diào)大模型慢第一個(gè)請(qǐng)求還沒(méi)處理完第二個(gè)、第三個(gè)重試請(qǐng)求就砸過(guò)來(lái)了。如果不做攔截你的系統(tǒng)就會(huì)把同一條提問(wèn)處理 3 遍自然就回了 3 遍。所謂的“冪等”就是保證不管同一個(gè)請(qǐng)求被重試了多少次業(yè)務(wù)邏輯永遠(yuǎn)只執(zhí)行一次。防御第一道防線接收側(cè)基于 MsgId 的秒級(jí)去重在防復(fù)讀機(jī)的戰(zhàn)役中第一步是“防接收重復(fù)”。 當(dāng)你配置好 Webhook 后客戶每發(fā)一條消息網(wǎng)關(guān)推過(guò)來(lái)的 JSON 里都會(huì)帶有一個(gè)全球唯一的身份標(biāo)識(shí)——MsgId。實(shí)戰(zhàn) JSON 載荷提取去重特征碼JSON{ MsgType: text, roomType: 2, ChatId: wr_xxxxxxxxxxxxxxxxxxxx, FromUserName: wm_xxxxxxxxxxxxxxxxxxxx, Content: 優(yōu)惠券怎么領(lǐng), MsgId: msg_xxxxxx_唯一的流水號(hào)_xxxxxx // 核心去重就靠它 }底層攔截邏輯大白話版收到這個(gè) JSON 后第一行代碼什么都別干先把MsgId拿出來(lái)去 Redis 里執(zhí)行一個(gè)SETNX如果不存在則設(shè)置操作。如果 Redis 告訴你設(shè)置成功說(shuō)明這是第一次收到這個(gè)消息放行去走業(yè)務(wù)邏輯。如果 Redis 告訴你已經(jīng)存在了說(shuō)明這是網(wǎng)關(guān)超時(shí)觸發(fā)的重試請(qǐng)求直接 return success當(dāng)做沒(méi)看見(jiàn)把重試請(qǐng)求直接扔進(jìn)垃圾桶。進(jìn)攻的藝術(shù)發(fā)送側(cè)利用 clientMsgId 確保不重發(fā)很多研發(fā)以為做好了接收側(cè)的去重就萬(wàn)事大吉了錯(cuò)在主動(dòng)調(diào)用 API 發(fā)消息的時(shí)候同樣有重發(fā)風(fēng)險(xiǎn)。假如你的代碼去調(diào)發(fā)消息接口消息其實(shí)已經(jīng)成功發(fā)到客戶手機(jī)上了但是在回傳 HTTP 200 給你的瞬間機(jī)房網(wǎng)絡(luò)閃斷了。你的代碼以為發(fā)送失敗于是又調(diào)了一次接口。得客戶又收到了兩遍。為了解決這個(gè)大坑你可以查閱 API文檔 里的高級(jí)發(fā)送參數(shù)我們會(huì)用到client_msg_id這個(gè)防重利器。實(shí)戰(zhàn) JSON 載荷帶防御盾的發(fā)消息JSON{ instance_guid: inst_xxxxxx, conversationId: wr_xxxxxxxxxxxxxxxxxxxx, msgtype: text, text: { content: 這是您的專屬滿減優(yōu)惠券鏈接... }, client_msg_id: req_1698765432_隨機(jī)數(shù) // 你自己生成的業(yè)務(wù)流水號(hào) }防重底層原理這個(gè)client_msg_id是你自己生成的可以用時(shí)間戳加隨機(jī)數(shù)或者關(guān)聯(lián)你們內(nèi)部的訂單ID。 當(dāng)你帶上這個(gè)參數(shù)打給網(wǎng)關(guān)時(shí)底層會(huì)把它緩存一小段時(shí)間。如果你因?yàn)榇a重試短時(shí)間內(nèi)又傳了同一個(gè)client_msg_id過(guò)來(lái)網(wǎng)關(guān)會(huì)直接返回成功但絕對(duì)不會(huì)向客戶再下發(fā)一次真實(shí)的微信消息。這就把重發(fā)風(fēng)險(xiǎn)死死按在了底層網(wǎng)關(guān)上。老司機(jī)的排障防坑鐵律冪等邏輯最大的難點(diǎn)在于它是一種“保護(hù)機(jī)制”平時(shí)網(wǎng)絡(luò)好的時(shí)候你根本感覺(jué)不到它的存在一旦高并發(fā)或者大促網(wǎng)絡(luò)擁堵沒(méi)有它你的系統(tǒng)瞬間原形畢露。所以千萬(wàn)別在生產(chǎn)環(huán)境當(dāng)小白鼠去測(cè)冪等我強(qiáng)烈建議各位研發(fā)兄弟在寫這套防重機(jī)制的時(shí)候必須祭出Apifox或者Apipost這類接口調(diào)試神器本地起好你的 Webhook 接口連上本地的 Redis。在 Apifox 里捏造一個(gè)帶MsgId的 JSON 報(bào)文。關(guān)鍵操作利用 Apifox 的并發(fā)測(cè)試功能針對(duì)同一個(gè) JSON瞬間發(fā)起 10 次并發(fā)請(qǐng)求盯著你的控制臺(tái)和日志如果你的數(shù)據(jù)庫(kù)里只存入了一條記錄且只觸發(fā)了一次大模型調(diào)用其他的 9 次全部被 Redis 擋住并直接返回了 success恭喜你你的去重鎧甲打磨成功了。把接收側(cè)的MsgId緩存攔截和發(fā)送側(cè)的client_msg_id穿透防御結(jié)合起來(lái)你的機(jī)器人才算是真正具備了工業(yè)級(jí)的抗壓能力。大家在寫分布式鎖或者組裝隨機(jī)數(shù)去重的時(shí)候如果卡殼了隨時(shí)在評(píng)論區(qū)貼出你的代碼片段咱們接著盤