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