<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          SpringBoot + Redis:模擬 10w 人的秒殺搶單!

          共 4227字,需瀏覽 9分鐘

           ·

          2021-12-17 15:46

          作者:神牛003
          來(lái)源:cnblogs.com/wangrudong003/p/10627539.html

          本篇內(nèi)容主要講解的是redis分布式鎖,這個(gè)在各大廠面試幾乎都是必備的,下面結(jié)合模擬搶單的場(chǎng)景來(lái)使用她;本篇不涉及到的redis環(huán)境搭建,快速搭建個(gè)人測(cè)試環(huán)境,這里建議使用docker;本篇內(nèi)容節(jié)點(diǎn)如下:

          Jedis的nx生成鎖

          • 如何刪除鎖
          • 模擬搶單動(dòng)作(10w個(gè)人開(kāi)搶)
          • jedis的nx生成鎖

          對(duì)于java中想操作redis,好的方式是使用jedis,首先pom中引入依賴:


          <dependency>
          ????<groupId>redis.clientsgroupId>
          ????<artifactId>jedisartifactId>
          dependency>

          對(duì)于分布式鎖的生成通常需要注意如下幾個(gè)方面:

          創(chuàng)建鎖的策略:?redis的普通key一般都允許覆蓋,A用戶set某個(gè)key后,B在set相同的key時(shí)同樣能成功,如果是鎖場(chǎng)景,那就無(wú)法知道到底是哪個(gè)用戶set成功的;這里jedis的setnx方式為我們解決了這個(gè)問(wèn)題,簡(jiǎn)單原理是:當(dāng)A用戶先set成功了,那B用戶set的時(shí)候就返回失敗,滿足了某個(gè)時(shí)間點(diǎn)只允許一個(gè)用戶拿到鎖。

          鎖過(guò)期時(shí)間:?某個(gè)搶購(gòu)場(chǎng)景時(shí)候,如果沒(méi)有過(guò)期的概念,當(dāng)A用戶生成了鎖,但是后面的流程被阻塞了一直無(wú)法釋放鎖,那其他用戶此時(shí)獲取鎖就會(huì)一直失敗,無(wú)法完成搶購(gòu)的活動(dòng);當(dāng)然正常情況一般都不會(huì)阻塞,A用戶流程會(huì)正常釋放鎖;過(guò)期時(shí)間只是為了更有保障。

          下面來(lái)上段setnx操作的代碼:

          public?boolean?setnx(String?key,?String?val)?{
          ????????Jedis?jedis?=?null;
          ????????try?{
          ????????????jedis?=?jedisPool.getResource();
          ????????????if?(jedis?==?null)?{
          ????????????????return?false;
          ????????????}
          ????????????return?jedis.set(key,?val,?"NX",?"PX",?1000?*?60).
          ????????????????????equalsIgnoreCase("ok");
          ????????}?catch?(Exception?ex)?{
          ????????}?finally?{
          ????????????if?(jedis?!=?null)?{
          ????????????????jedis.close();
          ????????????}
          ????????}
          ????????return?false;
          ????}

          這里注意點(diǎn)在于jedis的set方法,其參數(shù)的說(shuō)明如:

          • NX:是否存在key,存在就不set成功
          • PX:key過(guò)期時(shí)間單位設(shè)置為毫秒(EX:?jiǎn)挝幻耄?/section>

          setnx如果失敗直接封裝返回false即可,下面我們通過(guò)一個(gè)get方式的api來(lái)調(diào)用下這個(gè)setnx方法:


          @GetMapping("/setnx/{key}/{val}")
          public?boolean?setnx(@PathVariable?String?key,?@PathVariable?String?val)?{
          ?????return?jedisCom.setnx(key,?val);
          }

          訪問(wèn)如下測(cè)試url,正常來(lái)說(shuō)第一次返回了true,第二次返回了false,由于第二次請(qǐng)求的時(shí)候redis的key已存在,所以無(wú)法set成功

          由上圖能夠看到只有一次set成功,并key具有一個(gè)有效時(shí)間,此時(shí)已到達(dá)了分布式鎖的條件。

          如何刪除鎖

          上面是創(chuàng)建鎖,同樣的具有有效時(shí)間,但是我們不能完全依賴這個(gè)有效時(shí)間,場(chǎng)景如:有效時(shí)間設(shè)置1分鐘,本身用戶A獲取鎖后,沒(méi)遇到什么特殊情況正常生成了搶購(gòu)訂單后,此時(shí)其他用戶應(yīng)該能正常下單了才對(duì),但是由于有個(gè)1分鐘后鎖才能自動(dòng)釋放,那其他用戶在這1分鐘無(wú)法正常下單(因?yàn)殒i還是A用戶的),因此我們需要A用戶操作完后,主動(dòng)去解鎖:

          public?int?delnx(String?key,?String?val)?{
          ????????Jedis?jedis?=?null;
          ????????try?{
          ????????????jedis?=?jedisPool.getResource();
          ????????????if?(jedis?==?null)?{
          ????????????????return?0;
          ????????????}

          ????????????//if?redis.call('get','orderkey')=='1111'?then?return?redis.call('del','orderkey')?else?return?0?end
          ????????????StringBuilder?sbScript?=?new?StringBuilder();
          ????????????sbScript.append("if?redis.call('get','").append(key).append("')").append("=='").append(val).append("'").
          ????????????????????append("?then?").
          ????????????????????append("????return?redis.call('del','").append(key).append("')").
          ????????????????????append("?else?").
          ????????????????????append("????return?0").
          ????????????????????append("?end");

          ????????????return?Integer.valueOf(jedis.eval(sbScript.toString()).toString());
          ????????}?catch?(Exception?ex)?{
          ????????}?finally?{
          ????????????if?(jedis?!=?null)?{
          ????????????????jedis.close();
          ????????????}
          ????????}
          ????????return?0;
          ????}

          這里也使用了jedis方式,直接執(zhí)行l(wèi)ua腳本:根據(jù)val判斷其是否存在,如果存在就del;

          其實(shí)個(gè)人認(rèn)為通過(guò)jedis的get方式獲取val后,然后再比較value是否是當(dāng)前持有鎖的用戶,如果是那最后再刪除,效果其實(shí)相當(dāng);只不過(guò)直接通過(guò)eval執(zhí)行腳本,這樣避免多一次操作了redis而已,縮短了原子操作的間隔。(如有不同見(jiàn)解請(qǐng)留言探討);同樣這里創(chuàng)建個(gè)get方式的api來(lái)測(cè)試:

          @GetMapping("/delnx/{key}/{val}")
          public?int?delnx(@PathVariable?String?key,?@PathVariable?String?val)?{
          ???return?jedisCom.delnx(key,?val);
          }

          注意的是delnx時(shí),需要傳遞創(chuàng)建鎖時(shí)的value,因?yàn)橥ㄟ^(guò)et的value與delnx的value來(lái)判斷是否是持有鎖的操作請(qǐng)求,只有value一樣才允許del;

          模擬搶單動(dòng)作(10w個(gè)人開(kāi)搶)

          有了上面對(duì)分布式鎖的粗略基礎(chǔ),我們模擬下10w人搶單的場(chǎng)景,其實(shí)就是一個(gè)并發(fā)操作請(qǐng)求而已,由于環(huán)境有限,只能如此測(cè)試;如下初始化10w個(gè)用戶,并初始化庫(kù)存,商品等信息,如下代碼:

          //總庫(kù)存
          ????private?long?nKuCuen?=?0;
          ????//商品key名字
          ????private?String?shangpingKey?=?"computer_key";
          ????//獲取鎖的超時(shí)時(shí)間?秒
          ????private?int?timeout?=?30?*?1000;

          ????@GetMapping("/qiangdan")
          ????public?List?qiangdan()?{

          ????????//搶到商品的用戶
          ????????List?shopUsers?=?new?ArrayList<>();

          ????????//構(gòu)造很多用戶
          ????????List?users?=?new?ArrayList<>();
          ????????IntStream.range(0,?100000).parallel().forEach(b?->?{
          ????????????users.add("神牛-"?+?b);
          ????????});

          ????????//初始化庫(kù)存
          ????????nKuCuen?=?10;

          ????????//模擬開(kāi)搶
          ????????users.parallelStream().forEach(b?->?{
          ????????????String?shopUser?=?qiang(b);
          ????????????if?(!StringUtils.isEmpty(shopUser))?{
          ????????????????shopUsers.add(shopUser);
          ????????????}
          ????????});

          ????????return?shopUsers;
          ????}

          有了上面10w個(gè)不同用戶,我們?cè)O(shè)定商品只有10個(gè)庫(kù)存,然后通過(guò)并行流的方式來(lái)模擬搶購(gòu),如下?lián)屬?gòu)的實(shí)現(xiàn):

          /**
          ?????*?模擬搶單動(dòng)作
          ?????*
          ?????*?@param?b
          ?????*?@return
          ?????*/

          ????private?String?qiang(String?b)?{
          ????????//用戶開(kāi)搶時(shí)間
          ????????long?startTime?=?System.currentTimeMillis();

          ????????//未搶到的情況下,30秒內(nèi)繼續(xù)獲取鎖
          ????????while?((startTime?+?timeout)?>=?System.currentTimeMillis())?{
          ????????????//商品是否剩余
          ????????????if?(nKuCuen?<=?0)?{
          ????????????????break;
          ????????????}
          ????????????if?(jedisCom.setnx(shangpingKey,?b))?{
          ????????????????//用戶b拿到鎖
          ????????????????logger.info("用戶{}拿到鎖...",?b);
          ????????????????try?{
          ????????????????????//商品是否剩余
          ????????????????????if?(nKuCuen?<=?0)?{
          ????????????????????????break;
          ????????????????????}

          ????????????????????//模擬生成訂單耗時(shí)操作,方便查看:神牛-50?多次獲取鎖記錄
          ????????????????????try?{
          ????????????????????????TimeUnit.SECONDS.sleep(1);
          ????????????????????}?catch?(InterruptedException?e)?{
          ????????????????????????e.printStackTrace();
          ????????????????????}

          ????????????????????//搶購(gòu)成功,商品遞減,記錄用戶
          ????????????????????nKuCuen?-=?1;

          ????????????????????//搶單成功跳出
          ????????????????????logger.info("用戶{}搶單成功跳出...所剩庫(kù)存:{}",?b,?nKuCuen);

          ????????????????????return?b?+?"搶單成功,所剩庫(kù)存:"?+?nKuCuen;
          ????????????????}?finally?{
          ????????????????????logger.info("用戶{}釋放鎖...",?b);
          ????????????????????//釋放鎖
          ????????????????????jedisCom.delnx(shangpingKey,?b);
          ????????????????}
          ????????????}?else?{
          ????????????????//用戶b沒(méi)拿到鎖,在超時(shí)范圍內(nèi)繼續(xù)請(qǐng)求鎖,不需要處理
          //????????????????if?(b.equals("神牛-50")?||?b.equals("神牛-69"))?{
          //????????????????????logger.info("用戶{}等待獲取鎖...",?b);
          //????????????????}
          ????????????}
          ????????}
          ????????return?"";
          ????}

          這里實(shí)現(xiàn)的邏輯是:

          1、parallelStream():并行流模擬多用戶搶購(gòu)

          2、(startTime + timeout) >= System.currentTimeMillis():判斷未搶成功的用戶,timeout秒內(nèi)繼續(xù)獲取鎖

          3、獲取鎖前和后都判斷庫(kù)存是否還足夠

          4、jedisCom.setnx(shangpingKey, b):用戶獲取搶購(gòu)鎖

          5、獲取鎖后并下單成功,最后釋放鎖:jedisCom.delnx(shangpingKey, b)

          再來(lái)看下記錄的日志結(jié)果:

          最終返回?fù)屬?gòu)成功的用戶:

          程序汪資料鏈接

          程序汪接的7個(gè)私活都在這里,經(jīng)驗(yàn)整理

          Java項(xiàng)目分享 最新整理全集,找項(xiàng)目不累啦 06版

          堪稱神級(jí)的Spring Boot手冊(cè),從基礎(chǔ)入門到實(shí)戰(zhàn)進(jìn)階

          臥槽!字節(jié)跳動(dòng)《算法中文手冊(cè)》火了,完整版 PDF 開(kāi)放下載!

          臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開(kāi)放下載!

          字節(jié)跳動(dòng)總結(jié)的設(shè)計(jì)模式 PDF 火了,完整版開(kāi)放下載!


          歡迎添加程序汪個(gè)人微信 itwang009? 進(jìn)粉絲群或圍觀朋友圈

          瀏覽 26
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美经典怡红院肏肥屄淫荡视频在线观看 | 免费亚洲视频在线观看 | 亚洲成人免费在线观看 | 久色视频在线 | 99国精产品自偷自偷综合 |