摘要: 原創出處 https://www.bysocket.com 「公衆號:泥瓦匠BYSocket 」歡迎關注和轉載,保留摘要,謝謝!
目錄java
問題起源,微信小程序抽風 wx.request() 重複請求服務器提交數據。後端服務也很簡單,僞代碼以下:spring
class SignLogService { public void saveSignLog(SignLogDO log) { // 簡單插入作記錄 SignLogDAO.insert(log); } }
發現數據庫會存在重複數據行,提交時間如出一轍。但業務需求是不能有多餘的 log 出現,這明顯是個問題。數據庫
問題是,重複請求致使的數據重複插入。這問題形成的後果很明顯:小程序
問題如圖所示:後端
解決方式:如何將 同請求 A,不執行插入,而是讀取前一個請求插入的數據並返回。解決後流程應該以下:
微信小程序
上面說的那種業務場景:sign_log 表會有 user_id、sign_id、sign_time 等。那麼每次簽到,每一個人天天只有一條簽到記錄。springboot
數據庫層採起惟一索引的形式,保證數據記錄惟一性。即 UNIQUE 約束,UNIQUE 約束惟一標識數據庫表中的每條記錄。另外,user_id,sign_id,sign_time 三個組合適惟一字段。創表的僞代碼以下:服務器
CREATE TABLE sign_log ( id int NOT NULL, user_id int NOT NULL, sign_id int, sign_time int, CONSTRAINT unique_sign_log UNIQUE (user_id,sign_id,sign_time) )
重點是 CONSTRAINT unique_sign_log UNIQUE (user_id,sign_id,sign_time)
。有個小問題,數據量大的時候,每條記錄都會有對應的惟一索引,比較耗資源。那麼這樣就好了嗎?微信
答案是不行,服務不夠健壯。第一個請求插入成功,第二個請求直接報錯,Java 服務會拋出 DuplicateKeyException
。併發
簡單的冪等寫法操做便可,僞代碼以下:
class SignLogService { public SingLogDO saveSignLog(SignLogDO log) { // 冪等處理 SignLogDO insertLog = null; try { insertLog = signLogDAO.insert(log); } catch (DuplicateKeyException e) { insertLog = selectByUniqueKeys(userId,signId,signTime); } return insertLog; } }
的確,流量不是很大,也不算很高併發。重複寫問題,這樣處理便可。那大流量、高併發場景咋搞
流量大了後,單庫單表會演變成分庫分表。那麼基於單表的惟一索引形式,在碰到分表就沒法保證呢,插入的地方多是兩個分表 A1 和 A2。
解決思路:將數據的惟一性條件放到其餘存儲,並進行鎖控制
仍是上面的例子,天天,每次簽到,每一個人只有一條簽到記錄。那麼使用分佈式鎖 Redis 的解決方案。大體僞代碼以下:
// 加鎖 jedis.set(lockKey, requestId, "NX", "PX", expireTime);
// 解鎖 jedis.eval(script, lockKey,requestId);
class SignLogService { public SingLogDO saveSignLog(SignLogDO log) { // 冪等校驗 SignLogDO existLog = selectByUniqueKeys(userId,signId,signTime); if(Objects.nonNull(existLog)) { return existLog; } // 加鎖 jedis.set SignLogDO insertLog = signLogDAO.insert(log); // 解鎖 jedis.eval return insertLog; } }
這個方案仍是不是很成熟,你們參考下便可。
解決方案實戰中,瞭解具體術。概括以下: