当前位置: 首页 > 后端技术 > Java

SpringBoot+Redis:抗十万人,秒杀订单!

时间:2023-04-02 00:15:56 Java

作者:神牛003\来源:www.cnblogs.com/wangrudong003/p/10627539.html本文主要讲解redis分布式锁,几乎是各大厂面试必备。下面结合模拟抢单场景使用她;本文不涉及redis环境搭建,赶紧搭建个人测试环境。这里,推荐使用docker;本文内容节点如下:如何删除Jedisnx生成的锁来模拟抢单动作(10w个人开始抢单)jedis的nx生成的锁在java中操作redis,最好的方式是使用jedis,首先引入依赖intopom:redis.clientsjedisSpringBoot的基础就不介绍了。推荐这个实用教程:https://github.com/javastacks...对于分布式锁的生成,通常需要注意以下几个方面:创建锁的策略:一般允许覆盖普通的rediskey.用户A设置某个key后,B设置相同key也能成功。如果是锁场景,无法知道是哪个用户成功设置的;这里有jedis的setnx方法,为我们解决了这个问题。简单的原理就是:当用户A先设置成功,然后用户B在设置时返回失败,并且在某个时间点只允许一个用户获得锁。锁过期时间:在抢购场景下,如果没有过期的概念,当用户A产生了锁,但是后续进程阻塞,无法释放锁,其他用户总是会在这个时候获取不到锁时间无法完成抢购活动;当然一般情况下是不会阻塞的,A用户进程会正常释放锁;过期时间只是为了更安全。下面是上述setnx操作的代码:publicbooleansetnx(Stringkey,Stringval){Jedisjedis=null;试试{jedis=jedisPool.getResource();如果(jedis==null){返回假;}returnjedis.set(key,val,"NX","PX",1000*60).equalsIgnoreCase("ok");}catch(Exceptionex){}finally{if(jedis!=null){jedis.close();}}返回假;}这里需要注意的是jedis的set方法。参数解释如下:NX:是否有key,如果存在则设置不成功。PX:密钥过期时间的单位设置为毫秒(EX:以秒为单位)。封装返回false。接下来,我们通过getAPI调用setnx方法:@GetMapping("/setnx/{key}/{val}")publicbooleansetnx(@PathVariableStringkey,@PathVariableStringval){returnjedisCom.setnx(key,val);}访问以下测试url。通常,它第一次返回true,第二次返回false。由于rediskey在第二次请求中已经存在,所以无法setsuccess由上图我们可以看出只有一个set成功,并且key是有有效时间的。这个时候已经达到了分布式锁的条件。如何删除锁上面是创建锁,锁也是有有效时间的,但是我们不能完全依赖这个有效时间。例如设置有效时间为1分钟。用户A获得锁后,没有遇到特殊情况,正常生成抢购订单。之后,此时其他用户应该可以正常下单,但是由于锁只能在1分钟后自动释放,所以在这1分钟内其他用户无法正常下单(因为锁还是用户A的),所以我们需要在用户A完成操作后,主动解锁:publicintdelnx(Stringkey,Stringval){Jedisjedis=null;试试{jedis=jedisPool.getResource();如果(绝地武士==null){返回0;}//ifredis.call('get','orderkey')=='1111'thenreturnredis.call('del','orderkey')elsereturn0endStringBuildersbScript=newStringBuilder();sbScript.append("ifredis.call('get','").append(key).append("')").append("=='").append(val).append("'").append("then").append("returnredis.call('del','").append(key).append("')").append("else").append("返回0").append("结束");返回Integer.valueOf(jedis.eval(sbScript.toString()).toString());}catch(Exceptionex){}finally{if(jedis!=null){jedis.close();}}返回0;存在,如果存在,del;其实我个人认为,通过jedis的get方法获取到val之后,再比较这个值是否是当前持有锁的用户,如果是,那么在最后删除,效果其实是一样的;直接通过eval执行脚本即可,避免再次操作redis,缩短原子操作的间隔(如有不同意见欢迎留言讨论);还创建一个获取API来测试:@GetMapping("/delnx/{key}/{val}")publicintdelnx(@PathVariableStringkey,@PathVariableStringval){returnjedisCom.delnx(key,val);}注意,使用delnx时,需要传递创建锁时的值,因为et的值和delnx的值是用来判断是否是操作请求持有锁的,只有del的值相同只允许;模拟抢单动作(10万人开始抢单)有了上面分布式锁的粗略基础,我们模拟10万人抢单的场景,其实只是一个并发的操作请求。由于环境有限,我们只能这样测试;如下初始化100000个用户,并初始化库存、商品等信息,如下代码://totalinventoryprivatelongnKuCuen=0;//商品键名privateStringshangpingKey="computer_key";//获取锁的超时时间秒privateinttimeout=30*1000;@GetMapping("/qiangdan")publicListqiangdan(){//抢到商品的用户列表shopUsers=newArrayList<>();//构造多个用户列表users=newArrayList<>();IntStream.range(0,100000).parallel().forEach(b->{users.add("神牛-"+b);});//初始化库存nKuCuen=10;//模拟开抢users.parallelStream().forEach(b->{StringshopUser=qiang(b);如果(!StringUtils.isEmpty(shopUser)){shopUsers.add(shopUser);}});返回商店用户;}上面100000个不同的用户,我们设置只有10个商品有库存,然后通过并行流的方式模拟抢购,如下:/***模拟抢单动作**@paramb*@return*/privateStringqiang(Stringb){//用户开始抢时间longstartTime=System.currentTimeMillis();//30秒内未抢到继续获取锁while((startTime+timeout)>=System.currentTimeMillis()){//产品是否剩余if(nKuCuen<=0){break;}if(jedisCom.setnx(shangpingKey,b)){//用户b获得了锁logger.info("User{}获得了锁...",b);try{//产品是否剩余if(nKuCuen<=0){break;}//模拟生成订单的耗时操作方便查看:Godox-50次获取锁记录try{TimeUnit.SECONDS.sleep(1);}catch(InterruptedExceptione){e.printStackTrace();}//购买成功,商品减1,记录用户nKuCuen-=1;//logger.info("用户{}成功乱序跳转...剩余库存:{}",b,nKuCuen);returnb+"抢单成功,剩余库存:"+nKuCuen;}finally{logger.info("用户{}释放锁...",b);//释放锁jedisCom.delnx(shangpingKey,b);}}else{//用户b没有拿到锁,在超时范围内继续申请锁,不需要处理//if(b.equals("神牛-50")||b.equals("Godox-69")){//logger.info("User{}iswaitingtoacquirethelock...",b);//}}}return"";}这里实现的逻辑是:1.parallelStream():并行流模拟多用户抢购2.(startTime+timeout)>=System.currentTimeMillis():判断失败用户,timeout秒内继续获取锁3.获取锁前和4.jedisCom.delnx(shangpingKey,b):用户获取抢锁锁5.获取锁下单成功后,最后释放锁:jedisCom.delnx(shangpingKey,b)再看一下记录的日志结果:最终返回抢购成功的用户:近期热点文章推荐:1.1,000+Java面试题及答案(2021最新版)2.世界之最!Java协程来了。.3.玩大!Log4j2.x再次爆发。..4、SpringBoot2.6正式发布,一大波新特性。.5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!