<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 人的秒殺搶單

          共 4336字,需瀏覽 9分鐘

           ·

          2022-02-28 09:49

          往期熱門文章:

          1、別瞎寫工具類了,Spring自帶的挺香的!
          2、一口氣說出 Redis 16 個常見使用場景
          3、監(jiān)控員工離職傾向系統(tǒng)已被下架,網(wǎng)友:勸你善良
          4、同事說,我寫Java代碼像寫詩
          5、阿里p7和副處級干部選哪個?
          來源:www.cnblogs.com/wangrudong003/p/10627539.html

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

          Jedis的nx生成鎖

          • 如何刪除鎖
          • 模擬搶單動作(10w個人開搶)
          • jedis的nx生成鎖
          對于java中想操作redis,好的方式是使用jedis,首先pom中引入依賴:

          ????redis.clients
          ????jedis

          對于分布式鎖的生成通常需要注意如下幾個方面:
          創(chuàng)建鎖的策略: redis的普通key一般都允許覆蓋,A用戶set某個key后,B在set相同的key時同樣能成功,如果是鎖場景,那就無法知道到底是哪個用戶set成功的;這里jedis的setnx方式為我們解決了這個問題,簡單原理是:當A用戶先set成功了,那B用戶set的時候就返回失敗,滿足了某個時間點只允許一個用戶拿到鎖。
          鎖過期時間: 某個搶購場景時候,如果沒有過期的概念,當A用戶生成了鎖,但是后面的流程被阻塞了一直無法釋放鎖,那其他用戶此時獲取鎖就會一直失敗,無法完成搶購的活動;當然正常情況一般都不會阻塞,A用戶流程會正常釋放鎖;過期時間只是為了更有保障。
          下面來上段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;
          ????}
          這里注意點在于jedis的set方法,其參數(shù)的說明如:
          • NX:是否存在key,存在就不set成功
          • PX:key過期時間單位設(shè)置為毫秒(EX:單位秒)
          setnx如果失敗直接封裝返回false即可,下面我們通過一個get方式的api來調(diào)用下這個setnx方法:
          @GetMapping("/setnx/{key}/{val}")
          public?boolean?setnx(@PathVariable?String?key,?@PathVariable?String?val)?{
          ?????return?jedisCom.setnx(key,?val);
          }
          訪問如下測試url,正常來說第一次返回了true,第二次返回了false,由于第二次請求的時候redis的key已存在,所以無法set成功
          圖片
          由上圖能夠看到只有一次set成功,并key具有一個有效時間,此時已到達了分布式鎖的條件。

          如何刪除鎖

          上面是創(chuàng)建鎖,同樣的具有有效時間,但是我們不能完全依賴這個有效時間,場景如:有效時間設(shè)置1分鐘,本身用戶A獲取鎖后,沒遇到什么特殊情況正常生成了搶購訂單后,此時其他用戶應(yīng)該能正常下單了才對,但是由于有個1分鐘后鎖才能自動釋放,那其他用戶在這1分鐘無法正常下單(因為鎖還是A用戶的),因此我們需要A用戶操作完后,主動去解鎖:
          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;
          其實個人認為通過jedis的get方式獲取val后,然后再比較value是否是當前持有鎖的用戶,如果是那最后再刪除,效果其實相當;只不過直接通過eval執(zhí)行腳本,這樣避免多一次操作了redis而已,縮短了原子操作的間隔。(如有不同見解請留言探討);同樣這里創(chuàng)建個get方式的api來測試:
          @GetMapping("/delnx/{key}/{val}")
          public?int?delnx(@PathVariable?String?key,?@PathVariable?String?val)?{
          ???return?jedisCom.delnx(key,?val);
          }
          注意的是delnx時,需要傳遞創(chuàng)建鎖時的value,因為通過et的value與delnx的value來判斷是否是持有鎖的操作請求,只有value一樣才允許del;

          模擬搶單動作(10w個人開搶)

          有了上面對分布式鎖的粗略基礎(chǔ),我們模擬下10w人搶單的場景,其實就是一個并發(fā)操作請求而已,由于環(huán)境有限,只能如此測試;如下初始化10w個用戶,并初始化庫存,商品等信息,如下代碼:
          //總庫存
          ????private?long?nKuCuen?=?0;
          ????//商品key名字
          ????private?String?shangpingKey?=?"computer_key";
          ????//獲取鎖的超時時間?秒
          ????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);
          ????????});

          ????????//初始化庫存
          ????????nKuCuen?=?10;

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

          ????????return?shopUsers;
          ????}
          有了上面10w個不同用戶,我們設(shè)定商品只有10個庫存,然后通過并行流的方式來模擬搶購,如下?lián)屬彽膶崿F(xiàn):
          /**
          ?????*?模擬搶單動作
          ?????*
          ?????*?@param?b
          ?????*?@return
          ?????*/
          ????private?String?qiang(String?b)?{
          ????????//用戶開搶時間
          ????????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;
          ????????????????????}

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

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

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

          ????????????????????return?b?+?"搶單成功,所剩庫存:"?+?nKuCuen;
          ????????????????}?finally?{
          ????????????????????logger.info("用戶{}釋放鎖...",?b);
          ????????????????????//釋放鎖
          ????????????????????jedisCom.delnx(shangpingKey,?b);
          ????????????????}
          ????????????}?else?{
          ????????????????//用戶b沒拿到鎖,在超時范圍內(nèi)繼續(xù)請求鎖,不需要處理
          //????????????????if?(b.equals("神牛-50")?||?b.equals("神牛-69"))?{
          //????????????????????logger.info("用戶{}等待獲取鎖...",?b);
          //????????????????}
          ????????????}
          ????????}
          ????????return?"";
          ????}
          這里實現(xiàn)的邏輯是:
          1、parallelStream():并行流模擬多用戶搶購
          2、(startTime + timeout) >= System.currentTimeMillis():判斷未搶成功的用戶,timeout秒內(nèi)繼續(xù)獲取鎖
          3、獲取鎖前和后都判斷庫存是否還足夠
          4、jedisCom.setnx(shangpingKey, b):用戶獲取搶購鎖
          5、獲取鎖后并下單成功,最后釋放鎖:jedisCom.delnx(shangpingKey, b)
          再來看下記錄的日志結(jié)果:
          圖片
          最終返回搶購成功的用戶:

          往期熱門文章:

          1、滴滴程序員被親戚鄙視:年薪八十萬還不如二本教書的...
          2、IT界驚現(xiàn)文豪!華為領(lǐng)導(dǎo)及阿里P10遭吐槽

          3、上海地鐵乘車碼“變紅”,嚇倒一眾乘客,官方:為營造節(jié)日氣氛……
          4Spring Boot 項目打成 .exe 程序?實戰(zhàn)來了!
          5Spring Boot太重,Vert.x真香!
          6中美程序員不完全對比
          7、Spring Boot 3.0 M1 發(fā)布,正式棄用 Java 8,最低要求 Java 17。。。
          8、一個“扛住100億次請求”的春晚紅包系統(tǒng)
          9、你覺得HTTPS能防止重放攻擊嗎?
          10、數(shù)據(jù)一致性,為什么不推薦雙寫?

          瀏覽 44
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  久久色国产精品 | 免费一级无码婬片A片APP直播 | a62v无码在线 | 亚洲免费一级 | 日韩一级免费的视频 |