色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

高并發下怎么防止數據重復?

jf_ro2CN3Fa ? 來源:蘇三說技術 ? 作者:蘇三說技術 ? 2022-10-10 16:27 ? 次閱讀

前言

1. 需求

2. 性能優化

3. 出問題了

4. 多線程消費

5. 順序消費

6. 唯一索引

7. 分布式鎖

8. 統一mq異步處理

9. insert on duplicate key update

10. insert ignore

11. 防重表

前言

最近測試給我提了一個bug,說我之前提供的一個批量復制商品接口,產生了重復的商品數據。

追查原因之后發現,這個事情沒想象中簡單,可以說一波多折。

1. 需求

產品有個需求:用戶選擇一些品牌,點擊確定按鈕之后,系統需要基于一份默認品牌的商品數據,復制出一批新的商品。

拿到這個需求時覺得太簡單了,三下五除二就搞定。

我提供了一個復制商品的基礎接口,給商城系統調用。

當時的流程圖如下:

a00791e2-4857-11ed-a3b6-dac502259ad0.jpg

如果每次復制的商品數量不多,使用同步接口調用的方案問題也不大。

2. 性能優化

但由于每次需要復制的商品數量比較多,可能有幾千。

如果每次都是用同步接口的方式復制商品,可能會有性能問題。

因此,后來我把復制商品的邏輯改成使用mq異步處理。

改造之后的流程圖:

a02fcf68-4857-11ed-a3b6-dac502259ad0.jpg

復制商品的結果還需要通知商城系統:

a046f206-4857-11ed-a3b6-dac502259ad0.jpg

這個方案看起來,挺不錯的。

但后來出現問題了。

3. 出問題了

測試給我們提了一個bug,說我之前提供的一個批量復制商品的接口,產生了重復的商品數據。

經過追查之后發現,商城系統為了性能考慮,也改成異步了。

他們沒有在接口中直接調用基礎系統的復制商品接口,而是在job中調用的。

站在他們的視角流程圖是這樣的:

a05732e2-4857-11ed-a3b6-dac502259ad0.jpg

用戶調用商城的接口,他們會往請求記錄表中寫入一條數據,然后在另外一個job中,異步調用基礎系統的接口去復制商品。

但實際情況是這樣的:商城系統內部出現了bug,在請求記錄表中,同一條請求產生了重復的數據。這樣導致的結果是,在job中調用基礎系統復制商品接口時,發送了重復的請求。

剛好基礎系統現在是使用RocketMQ異步處理的。由于商城的job一次會取一批數據(比如:20條記錄),在極短的時間內(其實就是在一個for循環中)多次調用接口,可能存在相同的請求參數連續調用復制商品接口情況。于是,出現了并發插入重復數據的問題。

為什么會出現這個問題呢?

4. 多線程消費

RocketMQ的消費者,為了性能考慮,默認是用多線程并發消費的,最大支持64個線程。

例如:

@RocketMQMessageListener(topic="${com.susan.topic:PRODUCT_TOPIC}",
consumerGroup="${com.susan.group:PRODUCT_TOPIC_GROUP}")
@Service
publicclassMessageReceiverimplementsRocketMQListener{

@Override
publicvoidonMessage(MessageExtmessage){
Stringmessage=newString(message.getBody(),StandardCharsets.UTF_8);
doSamething(message);
}
}

也就是說,如果在極短的時間內,連續發送重復的消息,就會被不同的線程消費。

即使在代碼中有這樣的判斷:

ProductoldProduct=query(hashCode);
if(oldProduct==null){
productMapper.insert(product);
}

在插入數據之前,先判斷該數據是否已經存在,只有不存在才會插入。

但由于在并發情況下,不同的線程都判斷商品數據不存在,于是同時進行了插入操作,所以就產生了重復數據。

如下圖所示:

a0694d06-4857-11ed-a3b6-dac502259ad0.jpg

5. 順序消費

為了解決上述并發消費重復消息的問題,我們從兩方面著手:

商城系統修復產生重復記錄的bug。

基礎系統將消息改成單線程順序消費。

我仔細思考了一下,如果只靠商城系統修復bug,以后很難避免不出現類似的重復商品問題,比如:如果用戶在極短的時間內點擊創建商品按鈕多次,或者商城系統主動發起重試。

所以,基礎系統還需進一步處理。

其實RocketMQ本身是支持順序消費的,需要消息的生產者和消費者一起改。

生產者改為:

rocketMQTemplate.asyncSendOrderly(topic,message,hashKey,newSendCallback(){
@Override
publicvoidonSuccess(SendResultsendResult){
log.info("sendMessagesuccess");
}

@Override
publicvoidonException(Throwablee){
log.error("sendMessagefailed!");
}
});

重點是要調用rocketMQTemplate對象的asyncSendOrderly方法,發送順序消息。

消費者改為:

@RocketMQMessageListener(topic="${com.susan.topic:PRODUCT_TOPIC}",
consumeMode=ConsumeMode.ORDERLY,
consumerGroup="${com.susan.group:PRODUCT_TOPIC_GROUP}")
@Service
publicclassMessageReceiverimplementsRocketMQListener{

@Override
publicvoidonMessage(MessageExtmessage){
Stringmessage=newString(message.getBody(),StandardCharsets.UTF_8);
doSamething(message);
}
}

接收消息的重點是RocketMQMessageListener注解中的consumeMode參數,要設置成ConsumeMode.ORDERLY,這樣就能順序消費消息了。

a0a87332-4857-11ed-a3b6-dac502259ad0.jpg

兩邊都修改之后,復制商品這一塊就沒有再出現重復商品的問題了。

But,修完bug之后,我又思考了良久。

復制商品只是創建商品的其中一個入口,如果有其他入口,跟復制商品功能同時創建新商品呢?

不也會出現重復商品問題?

雖說,這種概率非常非常小。

但如果一旦出現重復商品問題,后續涉及到要合并商品的數據,非常麻煩。

經過這一次的教訓,一定要防微杜漸。

不管是用戶,還是自己的內部系統,從不同的入口創建商品,都需要解決重復商品創建問題。

那么,如何解決這個問題呢?

6. 唯一索引

解決重復商品數據問題,最快成本最低最有效的辦法是:給表建唯一索引。

想法是好的,但我們這邊有個規范就是:業務表必須都是邏輯刪除。

而我們都知道,要刪除表的某條記錄的話,如果用delete語句操作的話。

例如:

deletefromproductwhereid=123;

這種delete操作是物理刪除,即該記錄被刪除之后,后續通過sql語句基本查不出來。(不過通過其他技術手段可以找回,那是后話了)

還有另外一種是邏輯刪除,主要是通過update語句操作的。

例如:

updateproductsetdelete_status=1,edit_time=now(3)
whereid=123;

邏輯刪除需要在表中額外增加一個刪除狀態字段,用于記錄數據是否被刪除。在所有的業務查詢的地方,都需要過濾掉已經刪除的數據。

通過這種方式刪除數據之后,數據任然還在表中,只是從邏輯上過濾了刪除狀態的數據而已。

其實對于這種邏輯刪除的表,是沒法加唯一索引的。

為什么呢?

假設之前給商品表中的name和model加了唯一索引,如果用戶把某條記錄刪除了,delete_status設置成1了。后來,該用戶發現不對,又重新添加了一模一樣的商品。

由于唯一索引的存在,該用戶第二次添加商品會失敗,即使該商品已經被刪除了,也沒法再添加了。

這個問題顯然有點嚴重。

有人可能會說:把name、model和delete_status三個字段同時做成唯一索引不就行了?

答:這樣做確實可以解決用戶邏輯刪除了某個商品,后來又重新添加相同的商品時,添加不了的問題。但如果第二次添加的商品,又被刪除了。該用戶第三次添加相同的商品,不也出現問題了?

由此可見,如果表中有邏輯刪除功能,是不方便創建唯一索引的。

7. 分布式鎖

接下來,你想到的第二種解決數據重復問題的辦法可能是:加分布式鎖。

目前最常用的性能最高的分布式鎖,可能是redis分布式鎖了。

使用redis分布式鎖的偽代碼如下:

try{
Stringresult=jedis.set(lockKey,requestId,"NX","PX",expireTime);
if("OK".equals(result)){
doSamething();
returntrue;
}
returnfalse;
}finally{
unlock(lockKey,requestId);
}

不過需要在finally代碼塊中釋放鎖。

其中lockKey是由商品表中的name和model組合而成的,requestId是每次請求的唯一標識,以便于它每次都能正確得釋放鎖。還需要設置一個過期時間expireTime,防止釋放鎖失敗,鎖一直存在,導致后面的請求沒法獲取鎖。

如果只是單個商品,或者少量的商品需要復制添加,則加分布式鎖沒啥問題。

主要流程如下:

a0ce6916-4857-11ed-a3b6-dac502259ad0.jpg

可以在復制添加商品之前,先嘗試加鎖。如果加鎖成功,則在查詢商品是否存在,如果不存在,則添加商品。此外,在該流程中如果加鎖失敗,或者查詢商品時不存在,則直接返回。

加分布式鎖的目的是:保證查詢商品和添加商品的兩個操作是原子性的操作。

但現在的問題是,我們這次需要復制添加的商品數量很多,如果每添加一個商品都要加分布式鎖的話,會非常影響性能。

顯然對于批量接口,加redis分布式鎖,不是一個理想的方案。

8. 統一mq異步處理

前面我們已經聊過,在批量復制商品的接口,我們是通過RocketMQ的順序消息,單線程異步復制添加商品的,可以暫時解決商品重復的問題。

但那只改了一個添加商品的入口,還有其他添加商品的入口。

能不能把添加商品的底層邏輯統一一下,最終都調用同一段代碼。然后通過RocketMQ的順序消息,單線程異步添加商品。

主要流程如下圖所示:

a139edda-4857-11ed-a3b6-dac502259ad0.jpg

這樣確實能夠解決重復商品的問題。

但同時也帶來了另外兩個問題:

現在所有的添加商品功能都改成異步了,之前同步添加商品的接口如何返回數據呢?這就需要修改前端交互,否則會影響用戶體驗。

之前不同的添加商品入口,是多線程添加商品的,現在改成只能由一個線程添加商品,這樣修改的結果導致添加商品的整體效率降低了。

由此,綜合考慮了一下各方面因素,這個方案最終被否定了。

9. insert on duplicate key update

其實,在mysql中存在這樣的語法,即:insert on duplicate key update。

在添加數據時,mysql發現數據不存在,則直接insert。如果發現數據已經存在了,則做update操作。

不過要求表中存在唯一索引或PRIMARY KEY,這樣當這兩個值相同時,才會觸發更新操作,否則是插入。

現在的問題是PRIMARY KEY是商品表的主鍵,是根據雪花算法提前生成的,不可能產生重復的數據。

但由于商品表有邏輯刪除功能,導致唯一索引在商品表中創建不了。

由此,insert on duplicate key update這套方案,暫時也沒法用。

此外,insert on duplicate key update在高并發的情況下,可能會產生死鎖問題,需要特別注意一下。

10. insert ignore

在mysql中還存在這樣的語法,即:insert ... ignore。

在insert語句執行的過程中:mysql發現如果數據重復了,就忽略,否則就會插入。

它主要是用來忽略,插入重復數據產生的Duplicate entry 'XXX' for key 'XXXX'異常的。

不過也要求表中存在唯一索引或PRIMARY KEY。

但由于商品表有邏輯刪除功能,導致唯一索引在商品表中創建不了。

由此可見,這個方案也不行。

溫馨的提醒一下,使用insert ... ignore也有可能會導致死鎖。

11. 防重表

之前聊過,因為有邏輯刪除功能,給商品表加唯一索引,行不通。

后面又說了加分布式鎖,或者通過mq單線程異步添加商品,影響創建商品的性能。

那么,如何解決問題呢?

我們能否換一種思路,加一張防重表,在防重表中增加商品表的name和model字段作為唯一索引。

例如:

CREATETABLE`product_unique`(
`id`bigint(20)NOTNULLCOMMENT'id',
`name`varchar(130)DEFAULTNULLCOMMENT'名稱',
`model`varchar(255)NOTNULLCOMMENT'規格',
`user_id`bigint(20)unsignedNOTNULLCOMMENT'創建用戶id',
`user_name`varchar(30)NOTNULLCOMMENT'創建用戶名稱',
`create_date`datetime(3)NOTNULLDEFAULTCURRENT_TIMESTAMP(3)COMMENT'創建時間',
PRIMARYKEY(`id`),
UNIQUEKEY`ux_name_model`(`name`,`model`)
)ENGINE=InnoDBDEFAULTCHARSET=utf8mb4COMMENT='商品防重表';

其中表中的id可以用商品表的id,表中的name和model就是商品表的name和model,不過在這張防重表中增加了這兩個字段的唯一索引。

視野一下子被打開了。

在添加商品數據之前,先添加防重表。如果添加成功,則說明可以正常添加商品,如果添加失敗,則說明有重復數據。

防重表添加失敗,后續的業務處理,要根據實際業務需求而定。

如果業務上允許添加一批商品時,發現有重復的,直接拋異常,則可以提示用戶:系統檢測到重復的商品,請刷新頁面重試。

例如:

try{
transactionTemplate.execute((status)->{
productUniqueMapper.batchInsert(productUniqueList);
productMapper.batchInsert(productList);
returnBoolean.TRUE;
});
}catch(DuplicateKeyExceptione){
thrownewBusinessException("系統檢測到重復的商品,請刷新頁面重試");
}

在批量插入數據時,如果出現了重復數據,捕獲DuplicateKeyException異常,轉換成BusinessException這樣運行時的業務異常。

還有一種業務場景,要求即使出現了重復的商品,也不拋異常,讓業務流程也能夠正常走下去。

例如:

try{
transactionTemplate.execute((status)->{
productUniqueMapper.insert(productUnique);
productMapper.insert(product);
returnBoolean.TRUE;
});
}catch(DuplicateKeyExceptione){
product=productMapper.query(product);
}

在插入數據時,如果出現了重復數據,則捕獲DuplicateKeyException,在catch代碼塊中再查詢一次商品數據,將數據庫已有的商品直接返回。

如果調用了同步添加商品的接口,這里非常關鍵的一點,是要返回已有數據的id,業務系統做后續操作,要拿這個id操作。

當然在執行execute之前,還是需要先查一下商品數據是否存在,如果已經存在,則直接返回已有數據,如果不存在,才執行execute方法。這一步千萬不能少。

例如:

ProductoldProduct=productMapper.query(product);
if(Objects.nonNull(oldProduct)){
returnoldProduct;
}

try{
transactionTemplate.execute((status)->{
productUniqueMapper.insert(productUnique);
productMapper.insert(product);
returnBoolean.TRUE;
});
}catch(DuplicateKeyExceptione){
product=productMapper.query(product);
}
returnproduct;

千萬注意:防重表和添加商品的操作必須要在同一個事務中,否則會出問題。

順便說一下,還需要對商品的刪除功能做特殊處理一下,在邏輯刪除商品表的同時,要物理刪除防重表。用商品表id作為查詢條件即可。

說實話,解決重復數據問題的方案挺多的,沒有最好的方案,只有最適合業務場景的,最優的方案。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 數據
    +關注

    關注

    8

    文章

    7067

    瀏覽量

    89108
  • 代碼
    +關注

    關注

    30

    文章

    4791

    瀏覽量

    68685
  • 線程
    +關注

    關注

    0

    文章

    505

    瀏覽量

    19700
  • Redis
    +關注

    關注

    0

    文章

    376

    瀏覽量

    10882
  • 并發
    +關注

    關注

    0

    文章

    7

    瀏覽量

    2506

原文標題:去阿里面試到第二輪就被虐慘:高并發下怎么防止數據重復?

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    當ADC08D1520評估板在外部信號觸發下進行數據采集時,wavevison5能自動保存采集到的數據嗎?

    當ADC08D1520評估板在外部信號觸發下進行數據采集時,wavevison5能自動保存采集到的數據嗎?
    發表于 12-26 06:31

    ADS131采集數據重復的原因?

    下降沿采集,dataready觸發次數也是正常的,觸發頻率也正常,但是采集數據每兩個點重復一次,請問這有可能是什么原因導致的。
    發表于 11-14 06:44

    全雙工多路并發、低延時數傳解決行業信號擁堵問題

    SA618F30-FD全雙工透傳允許最多8個設備同時與主控制系統進行數據交互,而不造成信道沖突和延時。這對于智能電網、智能停車場、智能交通等需要實時傳輸大量數據的場景尤為重要。例如,智能電網中的多個監測設備需要實時將采集的
    的頭像 發表于 09-19 16:34 ?537次閱讀
    全雙工多路<b class='flag-5'>并發</b>、低延時數傳解決行業信號擁堵問題

    重復接地的作用與實施

    重復接地是一種在中性點直接接地的電力系統中采用的技術措施,通過在零干線的一個或多個位置用金屬導線將接地裝置連接起來。這 種措施主要應用于低壓三相四線制中性點直接接地的配電線路中。在施工過程中
    的頭像 發表于 08-15 11:23 ?944次閱讀

    并發物聯網云平臺是什么

    并發物聯網云平臺是一種能夠處理大量設備同時連接并進行數據交換的云計算平臺。這種平臺通常被設計用來應對來自數以萬計甚至數十億計的物聯網設備的并發請求,保證系統的穩定性和響應速度。 首先
    的頭像 發表于 08-13 13:50 ?265次閱讀

    并發系統的藝術:如何在流量洪峰中游刃有余

    前言 我們常說的三并發可用、高性能,這些技術是構建現代互聯網應用程序所必需的。對于京東618備戰來說,所有的中臺系統服務,無疑都是圍繞著三
    的頭像 發表于 08-05 13:43 ?294次閱讀
    <b class='flag-5'>高</b><b class='flag-5'>并發</b>系統的藝術:如何在流量洪峰中游刃有余

    ASMPT與美光攜手開發下一代HBM4鍵合設備

    在半導體制造技術的持續演進中,韓國后端設備制造商ASMPT與全球知名的內存解決方案提供商美光公司近日宣布了一項重要的合作。據悉,ASMPT已向美光提供了專用于帶寬內存(HBM)生產的演示熱壓(TC)鍵合機,雙方將攜手開發下一代鍵合技術,以支持HBM4的生產。
    的頭像 發表于 07-01 11:04 ?883次閱讀

    請問在使用AD9910的ram模式加載數據時,輸出電壓總會有一段擾動,應該如何解決呀

    ad9910在使用ram模式時單片機給他傳輸波形數據總是會出現一段先低后的波形,請問怎么消除這段波形,或者說問什么會出現這段波形,這段波形出現有什么規律嗎?請幫忙解答一下,謝謝各位佬。 就是這樣
    發表于 06-13 17:05

    請問ad9910芯片的ram模式在接收數據時為什么會出現一段方波?

    ad9910在使用ram模式時單片機給他傳輸波形數據總是會出現一段先低后的波形,請問怎么消除這段波形,或者說問什么會出現這段波形,這段波形出現有什么規律嗎?請幫忙解答一下,謝謝各位佬。 就是這樣
    發表于 06-12 10:25

    STM32通過DMA方式傳輸TIM3定時器計數值,數值重復是為什么?

    AD值防止數據錯亂,故DMA配置必須放在ADC之前。) 因此為了排除相同原因導致的數據重復,所以將DMA配置放在TIM3定時器初始化之前。 結果反饋,TIM3計數器數值
    發表于 04-10 06:50

    鴻蒙原生應用開發-ArkTS語言基礎類庫多線程并發概述

    的一系列復雜偶發的問題,同時并發度也相對較高,因此得到了廣泛的支持和使用,也是當前ArkTS語言選擇的并發模型。 由于Actor模型的內存隔離特性,所以需要進行跨線程的數據序列化傳輸。 一、
    發表于 03-28 14:35

    鴻蒙原生應用開發-ArkTS語言基礎類庫多線程并發概述

    的一系列復雜偶發的問題,同時并發度也相對較高,因此得到了廣泛的支持和使用,也是當前ArkTS語言選擇的并發模型。 由于Actor模型的內存隔離特性,所以需要進行跨線程的數據序列化傳輸。 一、
    發表于 03-22 15:40

    帶手動復位輸入的四重復位監控器TPS386596數據

    電子發燒友網站提供《帶手動復位輸入的四重復位監控器TPS386596數據表 .pdf》資料免費下載
    發表于 03-14 10:59 ?0次下載
    帶手動復位輸入的四<b class='flag-5'>重復</b>位監控器TPS386596<b class='flag-5'>數據</b>表

    HarmonyOS如何使用異步并發能力進行開發

    一、并發概述 并發是指在同一時間段內,能夠處理多個任務的能力。為了提升應用的響應速度與幀率,以及防止耗時任務對主線程的干擾,HarmonyOS系統提供了異步并發和多線程
    的頭像 發表于 02-18 09:18 ?539次閱讀

    RocketMQ中各類重復消費的原理淺析

    隨著大數據和云計算時代的到來,我國的各個產業每天都在產生不可估計的數據,以及對數據的各式各樣的需求,消息中間件在處理數據、消費數據的過程中越
    的頭像 發表于 01-08 09:29 ?1197次閱讀
    RocketMQ中各類<b class='flag-5'>重復</b>消費的原理淺析
    主站蜘蛛池模板: 麻豆乱码一卡二卡三卡视频| 国产精品一区二区人妻无码| 儿子操妈妈视频| 快播看黄片| 伊人22222| 精品手机在线视频| 亚洲精品蜜桃AV久久久| 国产九九九九九九九A片| 甜性涩爱下载| 涩涩在线观看免费视频| 白百合在线观看| 欧美做真爱欧免费看| adc免费观看| 欧美日本韩国一二区视频| 91涩涩视频| 欧美成人中文字幕在线视频| asian4you裸模| 日本激情在线| 艳鉧动漫1~6全集观看在线| WWW国产色情在线观看APP| 男人免费网站| a免费视频| 色姣姣狠狠撩综合网| 2019天天射干网站| 国产人妻麻豆蜜桃色69| 香蕉尹人综合精品| 国厂精品114福利电影| 亚洲精品久久国产高清| 久久精品成人免费看| 18禁黄久久久AAA片| 欧美影院在线观看完整版 mp4| 阿力gv资源| 校花爽好大快深点h| 九九99国产香蕉视频| 97国产视频| 色欲AV亚洲情无码AV蜜桃| 国产一区日韩二区欧美三区| 一二三四在线高清中文版免费观看电影 | 人人干人人插| 国产精品久久大陆| 日本高清免费看|