欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

關(guān)于Redis庫存超賣問題的分析

 更新時間:2024年11月05日 11:08:22   作者:洛上言  
在高并發(fā)場景下進行優(yōu)惠券秒殺測試時,發(fā)現(xiàn)由于并發(fā)操作導(dǎo)致了超賣問題,即理論上只能賣出100個優(yōu)惠券,實際賣出了102個,分析原因,是因為在高并發(fā)環(huán)境下,多個線程同時操作庫存,導(dǎo)致數(shù)據(jù)不一致,提出了兩種解決方案:悲觀鎖和樂觀鎖

一、分析問題

剛剛秒殺優(yōu)惠券購買測試的時候是我們自己在頁面上點擊進行測試的,這跟真實的秒殺場景還是有很大差異的,因為真實的秒殺場景下肯定有無數(shù)的用戶一起來搶購,一起來點購這個按鈕,因此一瞬間的并發(fā)量可能會達(dá)到每秒數(shù)百甚至上千、上萬的并發(fā),那我們這個結(jié)構(gòu)還能不能工作呢?

要想模擬這種高并發(fā)的場景,肯定要用到JeMter

image-20240527165204920

數(shù)據(jù)庫總量是100

image-20240527165224348

將訂單也清0

image-20240527165348190

接下來我們有100個券,我們希望的是只賣出100個,理論上來講只生成100個訂單。

啟動JeMeter,結(jié)果肯定有些成功有些失敗

image-20240527165842846

查看報告 49.25% 的異常率,跟我們預(yù)期有出入,我們的預(yù)期應(yīng)該是一般失敗

image-20240527170042389

回到數(shù)據(jù)庫中查看,可以看見訂單生成數(shù)量是 102

image-20240527170209669

并且?guī)齑孀優(yōu)榱?-2

image-20240527170238245

由此可見票出現(xiàn)了超賣,我們只能賣一百件,現(xiàn)在卻賣出了 102件,如果這件賣出的商品很貴重,這樣可能會給商家?guī)砭薮蟮膿p失。

那么我們?yōu)槭裁磿霈F(xiàn)這個問題呢?

有關(guān)超賣問題分析:在我們原有代碼中是這么寫的

if (voucher.getStock() < 1) {
    // 庫存不足
    return Result.fail("庫存不足!");
}
//5,扣減庫存
boolean success = seckillVoucherService.update()
    .setSql("stock= stock -1")
    .eq("voucher_id", voucherId).update();
if (!success) {
    //扣減庫存
    return Result.fail("庫存不足!");
}

正常情況下一個如下圖,一個執(zhí)行完再執(zhí)行另一個

image-20240527171052016

但是高并發(fā)的場景下,你就沒辦法控制線程的順序了,假設(shè)線程1過來查詢庫存,判斷出來庫存大于1,正準(zhǔn)備去扣減庫存,但是還沒有來得及去扣減,此時線程2過來,線程2也去查詢庫存,發(fā)現(xiàn)這個數(shù)量一定也大于1,那么這兩個線程都會去扣減庫存,最終多個線程相當(dāng)于一起去扣減庫存,此時就會出現(xiàn)庫存的超賣問題。

1653368335155

二、解決辦法

超賣問題是典型的多線程安全問題,針對這一問題的常見解決方案就是加鎖:而對于加鎖,我們通常有兩種解決方案:見下圖:

悲觀鎖和樂觀鎖并不是真正的鎖,它只是鎖設(shè)計的理念

image-20240527174028774

悲觀鎖:

如果我們多個線程是串行執(zhí)行的,就不會出現(xiàn)安全問題了。所以這就是悲觀鎖的實現(xiàn)思想,既然多線程并發(fā)有安全問題,那你就不要并發(fā)執(zhí)行了。正因為如此,悲觀鎖的性能就不是很好,因為你不管有多少線程,都只能一個一個的去執(zhí)行,因此高并發(fā)的場景下悲觀鎖并不是很適合。

JDK中提供的syn,和lock、數(shù)據(jù)庫中的互斥的鎖,都是悲觀鎖的代表,同時,悲觀鎖中又可以再細(xì)分為公平鎖,非公平鎖,可重入鎖,等等。

樂觀鎖:

因為樂觀鎖折后轉(zhuǎn)給你方案它不用加鎖,而是在執(zhí)行時才做一個判斷,因此它的性能要比悲觀鎖好很多。

但是它的關(guān)鍵點在于:我怎么知道在我更新的時候別人有沒有來做修改?因此這個判斷成為了關(guān)鍵,這也是我們接下來要研究的。

悲觀鎖比較簡單,相信大家都會,這里就不演示了,這里就演示樂觀鎖

三、樂觀鎖

判斷是否有人進行修改,常見的方式有兩種

實現(xiàn)方式一:版本號法

這種方案是應(yīng)用最廣泛的,也是最普遍的。

樂觀鎖:會有一個版本號,每次操作數(shù)據(jù)會對版本號+1,再提交回數(shù)據(jù)時,會去校驗是否比之前的版本大1 ,如果大1 ,則進行操作成功,這套機制的核心邏輯在于,如果在操作過程中,版本號只比原來大1 ,那么就意味著操作過程中沒有人對他進行過修改,他的操作就是安全的,如果不大1,則數(shù)據(jù)被修改過。

有了版本號后,線程1在做查詢的時候,就不僅僅是查庫存了,它還要將版本號也查出來,此時線程1查到的庫存和版本號是 1,緊接著,它本來要進行扣減了,但是此時另外一個線程插入進來了,此時就出現(xiàn)并發(fā)的問題了,此時線程二也去查詢,同樣也是查詢stock和version,查到的也是1。緊接著又切到了線程1,線程1要去扣減庫存,判斷庫存是否大于0,此時就要去扣減。

以前是直接扣減就完了,但是現(xiàn)在不行,版本號每次修改的時候都要加1,因此它在修改庫存的時候不僅僅要修改庫存,還需要修改版本號,因此在修改時,樂觀鎖的方案是:修改前先判斷一下,之前查詢到的數(shù)據(jù)是否被修改過,這里就是判斷版本是否被修改過, where version = 1,因為之前查詢出來的version是1,如果執(zhí)行這個條件時version依然等于1,說明跟我們之前查詢到的一樣,說明在我執(zhí)行修改之前,是沒有人修改過這個數(shù)據(jù)的,既然沒有人修改過,我就可以放心大膽的去減了。

那么第一個線程在操作后,數(shù)據(jù)庫中的version變成了2,但是他自己滿足version=1 ,所以沒有問題,此時線程2執(zhí)行,線程2 最后也需要加上條件version =1 ,但是現(xiàn)在由于線程1已經(jīng)操作過了,所以線程2,操作時就不滿足version=1 的條件了,所以線程2無法執(zhí)行成功

image-20240527181157966

實現(xiàn)方式二:CAS

在實現(xiàn)方式一的基礎(chǔ)上做了簡化,版本號法其實是用版本來表示版本是否變化,其次在更新的時候每次除了數(shù)據(jù)以外,版本也要跟著更新,既然每次更新都要更新版本,如果我查到的版本跟我更新時的版本一致,證明就沒有人更新。但是大家看一下我們當(dāng)前的業(yè)務(wù),我們每一次業(yè)務(wù),其實在查詢版本的時候庫存也都跟著查出來了,更新的時候庫存也要更新,可以發(fā)現(xiàn)庫存跟版本所做的事是一樣的,既然如此為什么不使用庫存代替版本?我在查詢的時候?qū)齑娌槌鰜?,然后我在更新的時候當(dāng)前的這個庫存跟之前查到的庫存是不是一樣的,如果一樣,不就同樣可以證明沒有人修改過嗎?

用數(shù)據(jù)本身有沒有變化來判斷線程是否安全,這種方案就稱之為cas(compare and set),先比較然后修改。

核心思路和上面差不多

image-20240527182236849

下面是補充,老師沒講的。

利用cas進行無鎖化機制加鎖,var5 是操作前讀取的內(nèi)存值,while中的var1+var2 是預(yù)估值,如果預(yù)估值 == 內(nèi)存值,則代表中間沒有被人修改過,此時就將新值去替換 內(nèi)存值

其中do while 是為了在操作失敗時,再次進行自旋操作,即把之前的邏輯再操作一次。

int var5;
do {
    var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

return var5;

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Redis遠(yuǎn)程連接Redis客戶端的實現(xiàn)步驟

    Redis遠(yuǎn)程連接Redis客戶端的實現(xiàn)步驟

    本文主要介紹了Redis遠(yuǎn)程連接Redis客戶端的實現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • 簡單聊一聊redis過期時間的問題

    簡單聊一聊redis過期時間的問題

    在使用redis的過期時間時不由想到設(shè)置了過期時間,下面這篇文章主要給大家介紹了關(guān)于redis過期時間問題的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-04-04
  • redis中使用lua腳本的原理與基本使用詳解

    redis中使用lua腳本的原理與基本使用詳解

    在?Redis?中使用?Lua?腳本可以實現(xiàn)原子性操作、減少網(wǎng)絡(luò)開銷以及提高執(zhí)行效率,下面小編就來和大家詳細(xì)介紹一下在redis中使用lua腳本的原理與基本使用吧
    2025-04-04
  • Redis核心原理詳細(xì)解說

    Redis核心原理詳細(xì)解說

    這篇文章主要介紹了Redis核心原理詳細(xì)解說,redis利用epoll實現(xiàn)IO多路復(fù)用,將連接信息和事件放到隊列中,依次放到文件事件分派器,事件分派器將事件分發(fā)給事件處理器
    2022-07-07
  • Redis中的延遲雙刪

    Redis中的延遲雙刪

    這篇文章主要介紹了Redis中的延遲雙刪問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • macOS上Redis的安裝與測試操作

    macOS上Redis的安裝與測試操作

    這篇文章主要介紹了macOS上Redis的安裝與測試操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-07-07
  • linux?redis-連接命令解讀

    linux?redis-連接命令解讀

    這篇文章主要介紹了linux?redis-連接命令解讀,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • 圖解Redis主從復(fù)制與Redis哨兵機制

    圖解Redis主從復(fù)制與Redis哨兵機制

    這篇文章主要介紹了圖解Redis主從復(fù)制與Redis哨兵機制,今天分享一下Redis的持久化、事務(wù)、管道相關(guān)的知識點,需要的朋友可以參考下
    2023-03-03
  • redis間歇性斷連解決方式

    redis間歇性斷連解決方式

    本文主要介紹了在使用lettuce4.2.2.Final版本作為Redis客戶端時遇到的間歇性斷連問題,具有一定的參考價值,感興趣的可以了解一下
    2025-03-03
  • Redis6.0搭建集群Redis-cluster的方法

    Redis6.0搭建集群Redis-cluster的方法

    這篇文章主要介紹了Redis6.0搭建集群Redis-cluster的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05

最新評論