摘要:?jiǎn)栴}是,重復(fù)請(qǐng)求導(dǎo)致的數(shù)據(jù)重復(fù)插入。這問(wèn)題造成的后果很明顯數(shù)據(jù)冗余,可能不單單多一條有些業(yè)務(wù)需求不能有多余數(shù)據(jù),造成服務(wù)問(wèn)題問(wèn)題如圖所示解決方式如何將同請(qǐng)求,不執(zhí)行插入,而是讀取前一個(gè)請(qǐng)求插入的數(shù)據(jù)并返回。那么使用分布式鎖的解決方案。
摘要: 原創(chuàng)出處 https://www.bysocket.com 「公眾號(hào):泥瓦匠BYSocket 」歡迎關(guān)注和轉(zhuǎn)載,保留摘要,謝謝!
目錄
為啥要解決數(shù)據(jù)重復(fù)插入?
解決方案實(shí)戰(zhàn)
可落地小總結(jié)
一、為啥要解決數(shù)據(jù)重復(fù)插入?問(wèn)題起源,微信小程序抽風(fēng) wx.request() 重復(fù)請(qǐng)求服務(wù)器提交數(shù)據(jù)。后端服務(wù)也很簡(jiǎn)單,偽代碼如下:
class SignLogService { public void saveSignLog(SignLogDO log) { // 簡(jiǎn)單插入做記錄 SignLogDAO.insert(log); } }
發(fā)現(xiàn)數(shù)據(jù)庫(kù)會(huì)存在重復(fù)數(shù)據(jù)行,提交時(shí)間一模一樣。但業(yè)務(wù)需求是不能有多余的 log 出現(xiàn),這明顯是個(gè)問(wèn)題。
問(wèn)題是,重復(fù)請(qǐng)求導(dǎo)致的數(shù)據(jù)重復(fù)插入。這問(wèn)題造成的后果很明顯:
數(shù)據(jù)冗余,可能不單單多一條
有些業(yè)務(wù)需求不能有多余數(shù)據(jù),造成服務(wù)問(wèn)題
問(wèn)題如圖所示:
解決方式:如何將 同請(qǐng)求 A,不執(zhí)行插入,而是讀取前一個(gè)請(qǐng)求插入的數(shù)據(jù)并返回。解決后流程應(yīng)該如下:
唯一索引 + 唯一字段
冪等
上面說(shuō)的那種業(yè)務(wù)場(chǎng)景:sign_log 表會(huì)有 user_id、sign_id、sign_time 等。那么每次簽到,每個(gè)人每天只有一條簽到記錄。
數(shù)據(jù)庫(kù)層采取唯一索引的形式,保證數(shù)據(jù)記錄唯一性。即 UNIQUE 約束,UNIQUE 約束唯一標(biāo)識(shí)數(shù)據(jù)庫(kù)表中的每條記錄。另外,user_id,sign_id,sign_time 三個(gè)組合適唯一字段。創(chuàng)表的偽代碼如下:
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) )
重點(diǎn)是 CONSTRAINT unique_sign_log UNIQUE (user_id,sign_id,sign_time)。有個(gè)小問(wèn)題,數(shù)據(jù)量大的時(shí)候,每條記錄都會(huì)有對(duì)應(yīng)的唯一索引,比較耗資源。那么這樣就行了嗎?
答案是不行,服務(wù)不夠健壯。第一個(gè)請(qǐng)求插入成功,第二個(gè)請(qǐng)求直接報(bào)錯(cuò),Java 服務(wù)會(huì)拋出 DuplicateKeyException 。
簡(jiǎn)單的冪等寫(xiě)法操作即可,偽代碼如下:
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; } }
的確,流量不是很大,也不算很高并發(fā)。重復(fù)寫(xiě)問(wèn)題,這樣處理即可。那大流量、高并發(fā)場(chǎng)景咋搞
2.分庫(kù)分表解決方案流量大了后,單庫(kù)單表會(huì)演變成分庫(kù)分表。那么基于單表的唯一索引形式,在碰到分表就無(wú)法保證呢,插入的地方可能是兩個(gè)分表 A1 和 A2。
解決思路:將數(shù)據(jù)的唯一性條件放到其他存儲(chǔ),并進(jìn)行鎖控制
還是上面的例子,每天,每次簽到,每個(gè)人只有一條簽到記錄。那么使用分布式鎖 Redis 的解決方案。大致偽代碼如下:
a.加鎖// 加鎖 jedis.set(lockKey, requestId, "NX", "PX", expireTime);
lockKey 最簡(jiǎn)單的是 user_id + sign_id + sign_time
expireTime 設(shè)置為一天
b.解鎖// 解鎖 jedis.eval(script, lockKey,requestId);c.冪等代碼加強(qiáng)
class SignLogService { public SingLogDO saveSignLog(SignLogDO log) { // 冪等校驗(yàn) SignLogDO existLog = selectByUniqueKeys(userId,signId,signTime); if(Objects.nonNull(existLog)) { return existLog; } // 加鎖 jedis.set SignLogDO insertLog = signLogDAO.insert(log); // 解鎖 jedis.eval return insertLog; } }
這個(gè)方案還是不是很成熟,大家參考下即可。
三、可落地小總結(jié)解決方案實(shí)戰(zhàn)中,了解具體術(shù)。歸納如下:
冪等:保證多次同意請(qǐng)求后結(jié)果一致
并發(fā)控制:?jiǎn)伪砦ㄒ凰饕?、分布式多表分布式鎖
降級(jí)兜底方案:分布式鎖鎖失效 - 考慮樂(lè)觀鎖兜底
參考資料重復(fù)插入方案: http://www.bysocket.com/archi...
《阿里巴巴 Java 開(kāi)發(fā)手冊(cè)》
以下專(zhuān)題教程也許您會(huì)有興趣《Spring Boot 2.x 系列教程》
《Java 核心系列教程》
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/77619.html
摘要:小明馬上開(kāi)發(fā)完畢,成功上線。下班過(guò)后,小明回想大紅說(shuō)的話,什么是間隙鎖,什么是插入意向鎖,看來(lái)作為開(kāi)發(fā)者對(duì)數(shù)據(jù)庫(kù)不應(yīng)該只會(huì)寫(xiě)啊,不然遇到一些疑難雜癥完全沒(méi)法解決啊。破壞了數(shù)據(jù)庫(kù)中的隔離性。 1.鎖? 1.1何為鎖 鎖在現(xiàn)實(shí)中的意義為:封閉的器物,以鑰匙或暗碼開(kāi)啟。在計(jì)算機(jī)中的鎖一般用來(lái)管理對(duì)共享資源的并發(fā)訪問(wèn),比如我們java同學(xué)熟悉的Lock,synchronized等都是我們常見(jiàn)的...
閱讀 3389·2023-04-25 22:47
閱讀 3876·2021-10-11 10:59
閱讀 2366·2021-09-07 10:12
閱讀 4352·2021-08-11 11:15
閱讀 3492·2019-08-30 13:15
閱讀 1812·2019-08-30 13:00
閱讀 1029·2019-08-29 14:02
閱讀 1743·2019-08-26 13:57