高并發(fā)場景下模擬商品秒殺

1.簡介

通過模擬搶購商品的實(shí)踐闡述高并發(fā)與鎖的問題临梗。這里假設(shè)電商網(wǎng)站搶購的場景假勿,電商網(wǎng)站往往存在很多的商品呜呐,有些商品會(huì)以低價(jià)限量推銷灾票,并且會(huì)在推銷之前做廣告以吸引網(wǎng)站會(huì)員購買寸宵。特別是熱銷產(chǎn)品巩检,很有可能會(huì)出現(xiàn)瞬時(shí)高并發(fā)的搶購患久,也就是我們常說的“商品秒殺”椅寺。這種情況在工作中很是常見,而且在面試的時(shí)候往往也是一個(gè)熱點(diǎn)考察的問題蒋失,下面就由我來給大家講解下如何處理這類高并發(fā)問題返帕。

2.項(xiàng)目準(zhǔn)備

首先,我先在redis中放入stock和商品數(shù)量篙挽,數(shù)量為100荆萤。
然后再通過jmeter來模擬高并發(fā)場景。

3.項(xiàng)目實(shí)踐

3.1 單機(jī)服務(wù)版本代碼

首先铣卡,我們先來看一個(gè)基礎(chǔ)的版本链韭。

@RequestMapping("/getStock")
    public String getStock(){
        //加鎖
        synchronized (this){
            //從redis中取庫存
            int stock = Integer.parseInt(redisTemplate.opsForValue().get(STOCK));
            //判斷剩余庫存
            if(stock>0){
                //如果還有庫存,則購買
                int newStock = stock-1;
                //再放回redis
                redisTemplate.opsForValue().set(STOCK,String.valueOf(newStock));
                System.out.println("扣減成功煮落,當(dāng)前庫存為:"+newStock);
            }else {
                System.out.println("扣減【失敗】梧油,當(dāng)前庫存為:"+stock);
            }
            return "success";
        }

扣減成功,當(dāng)前庫存為:99
扣減成功州邢,當(dāng)前庫存為:98
扣減成功儡陨,當(dāng)前庫存為:97
………………
扣減成功,當(dāng)前庫存為:9
扣減成功量淌,當(dāng)前庫存為:8
扣減成功骗村,當(dāng)前庫存為:7
扣減成功,當(dāng)前庫存為:6
扣減成功呀枢,當(dāng)前庫存為:5
扣減成功胚股,當(dāng)前庫存為:4
扣減成功,當(dāng)前庫存為:3
扣減成功裙秋,當(dāng)前庫存為:2
扣減成功琅拌,當(dāng)前庫存為:1
扣減成功缨伊,當(dāng)前庫存為:0
扣減【失敗】,當(dāng)前庫存為:0
扣減【失敗】进宝,當(dāng)前庫存為:0
扣減【失敗】刻坊,當(dāng)前庫存為:0
扣減【失敗】,當(dāng)前庫存為:0
扣減【失敗】党晋,當(dāng)前庫存為:0
…………

從結(jié)果看來谭胚,這樣的代碼在單機(jī)環(huán)境下沒有什么問題
但如果是分布式部署,那這樣就會(huì)出現(xiàn)超賣現(xiàn)象未玻!


image.png
image.png

很顯然灾而,上面的代碼并不滿足分布式環(huán)境下的系統(tǒng)需求,下面我們將使用分布式鎖來改進(jìn)這個(gè)問題扳剿。

3.2 分布式服務(wù)版本代碼

在這個(gè)2.0的版本里旁趟,我們使用redis中的setnx數(shù)據(jù)結(jié)構(gòu)來做分布式鎖。

    private static String LOCK_KEY = "lockKey";

    @RequestMapping("/getStock2")
    public String getStock2() {
        //如果這里不設(shè)置過期時(shí)間庇绽,很可能會(huì)出現(xiàn)問題锡搜,例如其中一臺(tái)機(jī)器未釋放鎖便出現(xiàn)異常或宕機(jī)敛劝,那后面的請求都無法購買商品
        Boolean result = redisTemplate.opsForValue()
                .setIfAbsent(LOCK_KEY, "lock", 10, TimeUnit.SECONDS);
        if (!result) {
            return "活動(dòng)太火爆了,請稍后重試";
        }

        try {
            int stock = Integer.parseInt(redisTemplate.opsForValue().get(STOCK));
            if (stock > 0) {
                int newStock = stock - 1;
                redisTemplate.opsForValue().set(STOCK, String.valueOf(newStock));
                System.out.println("扣減成功纷宇,扣減后庫存為:" + newStock);
            } else {
                System.out.println("扣減【失敗】夸盟,當(dāng)前庫存為:" + stock);
            }
        } finally {
            //此處要盡量保證鎖的釋放
            redisTemplate.delete(LOCK_KEY);
        }
        return "success";
    }

上面這個(gè)代碼,在并發(fā)不太高的情況下像捶,基本可以使用上陕,然而在高并發(fā)情況下依然存在鎖失效問題。

image.png

解決這個(gè)問題的關(guān)鍵是拓春,我自己加的鎖應(yīng)該只能由我自己釋放释簿。

3.3 使用redisson實(shí)現(xiàn)分布式鎖

要實(shí)現(xiàn)一個(gè)好的分布式鎖,應(yīng)包含以下功能:

  1. 指定一個(gè) key 作為鎖標(biāo)記硼莽,存入 Redis 中庶溶,指定一個(gè) 唯一的用戶標(biāo)識 作為 value。
  2. 當(dāng) key 不存在時(shí)才能設(shè)置值懂鸵,確保同一時(shí)間只有一個(gè)客戶端進(jìn)程獲得鎖偏螺,滿足 互斥性 特性。
  3. 設(shè)置一個(gè)過期時(shí)間匆光,防止因系統(tǒng)異常導(dǎo)致沒能刪除這個(gè) key套像,滿足 防死鎖 特性。
  4. 當(dāng)處理完業(yè)務(wù)之后需要清除這個(gè) key 來釋放鎖终息,清除 key 時(shí)需要校驗(yàn) value 值夺巩,需要滿足 只有加鎖的人才能釋放鎖 贞让。

這些我們都可以通過redisson來輕松的使用分布式鎖,代碼如下:

 @RequestMapping("/getStock3")
    public String getStock3() {
        RLock rLock = redisson.getLock(LOCK_KEY);
        try {
            rLock.lock();
            int stock = Integer.parseInt(redisTemplate.opsForValue().get(STOCK));
            if (stock > 0) {
                int newStock = stock - 1;
                redisTemplate.opsForValue().set(STOCK, String.valueOf(newStock));
                System.out.println("扣減成功柳譬,扣減后庫存為:" + newStock);
            } else {
                System.out.println("扣減【失敗】喳张,當(dāng)前庫存為:" + stock);
            }
        } finally {
            rLock.unlock();
        }
        return "success";
    }

在大部分情況下,我們都可以使用redisson來完成我們的分布式鎖征绎,來應(yīng)對高并發(fā)的問題蹲姐,

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市人柿,隨后出現(xiàn)的幾起案子柴墩,更是在濱河造成了極大的恐慌,老刑警劉巖凫岖,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件江咳,死亡現(xiàn)場離奇詭異,居然都是意外死亡哥放,警方通過查閱死者的電腦和手機(jī)歼指,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來甥雕,“玉大人踩身,你說我怎么就攤上這事∩缏叮” “怎么了挟阻?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長峭弟。 經(jīng)常有香客問我附鸽,道長,這世上最難降的妖魔是什么瞒瘸? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任坷备,我火速辦了婚禮,結(jié)果婚禮上情臭,老公的妹妹穿的比我還像新娘省撑。我一直安慰自己,他們只是感情好俯在,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布丁侄。 她就那樣靜靜地躺著,像睡著了一般朝巫。 火紅的嫁衣襯著肌膚如雪鸿摇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天劈猿,我揣著相機(jī)與錄音拙吉,去河邊找鬼潮孽。 笑死,一個(gè)胖子當(dāng)著我的面吹牛筷黔,可吹牛的內(nèi)容都是我干的往史。 我是一名探鬼主播,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼佛舱,長吁一口氣:“原來是場噩夢啊……” “哼椎例!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起请祖,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤订歪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后肆捕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刷晋,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年慎陵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了眼虱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,769評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡席纽,死狀恐怖捏悬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情润梯,我是刑警寧澤过牙,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站仆救,受9級特大地震影響抒和,放射性物質(zhì)發(fā)生泄漏矫渔。R本人自食惡果不足惜彤蔽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望庙洼。 院中可真熱鬧顿痪,春花似錦、人聲如沸油够。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽石咬。三九已至揩悄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鬼悠,已是汗流浹背删性。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工亏娜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蹬挺。 一個(gè)月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓维贺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親巴帮。 傳聞我的和親對象是個(gè)殘疾皇子溯泣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內(nèi)容