Redis 秒杀案例
实现ab工具模拟并发超卖和超时问题解决
配置JedisPool连接池来解决超时问题利用乐观锁淘汰用户,解决超卖问题 库存遗留问题解决
什么是Lua脚本Lua脚本在redis中的优势编写Lua脚本
实现写一个简单的springboot + thymeleaf页面示例
Title iPhone 13 Pro !!! 1元秒杀
controller
@PostMapping("/doseckill") @ResponseBody public String doseckill(String prodid) throws IOException { String userid = new Random().nextInt(50000) + ""; boolean isSuccess = SecKill_redis.doSecKill(userid, prodid); return JSON.toJSONString(isSuccess); }
秒杀过程
// 秒杀过程 public static boolean doSecKill(String uid,String prodid) throws IOException{ //1.uid和prodid非空判断 if (uid == null || prodid == null){ return false; } //2.连接redis Jedis jedis = new Jedis("192.168.0.2",6379); jedis.auth("password"); //3.拼接Key //3.1 库存key String kcKey = "sk:"+prodid+":qt"; //3.2 用户key String userKey = "sk:"+prodid+":user"; //4. 获取库存,如果库存为null,秒杀还没又开始 String kc = jedis.get(kcKey); if (kc == null){ System.out.println("秒杀还没有开始请等待"); jedis.close(); return false; } //5. 判断用户是否重复秒杀 *** 作 if(jedis.sismember(userKey,uid)//命令判断成员元素是否是集合的成员 ){ System.out.println("已经成功秒杀"); jedis.close(); return false; } //6 判断如果商品数量,库存数量小于1,秒杀结束 if (Integer.parseInt(kc) < 1){ System.out.println("秒杀已经结束"); jedis.close(); return false; } //7 秒杀过程 //7.1 库存-1 jedis.decr(kcKey); //7.2 把秒杀成功的用户添加到清单里面 jedis.sadd(userKey,uid); System.out.println("秒杀成功"); jedis.close(); return true; }
redis 中添加库存
set sk:0101:qt 10
点击秒杀
查看控制台输出情况
查看redis,可以看到库存已清空,并且用户id添加到秒杀成功的集合中
ab工具模拟并发为了模拟并发的效果,我们使用工具ab模拟测测试
centos7 安装
yum install httpd-tools
ab模拟提交post请求
在linux中创建postfile文件
prodid=0101&
在postfile所在的目录执行命令,1000个请求100个并发
ab -n 1000 -c 100 -p /home/xm/postfile -T application/x-www-form-urlencoded http://192.168.2.2:8080/doseckill
查看控制台和redis中的数据,发现问题
还出现了连接超时的问题
超卖和超时问题解决 配置JedisPool连接池来解决超时问题编写工具类
public class JedisPoolUtil { private static volatile JedisPool jedisPool = null; private JedisPoolUtil() { } public static JedisPool getJedisPoolInstance(){ if (null == jedisPool){ synchronized (JedisPoolUtil.class){ if (null == jedisPool){ JedisPoolConfig poolConfig = new JedisPoolConfig(); poolConfig.setMaxTotal(200); poolConfig.setMaxIdle(32); poolConfig.setMaxWaitMillis(100*1000); poolConfig.setBlockWhenExhausted(true); poolConfig.setTestOnBorrow(true); jedisPool = new JedisPool(poolConfig,"192.168.2.2",6379,60000,"password"); } } } return jedisPool; } public static void release(JedisPool jedisPool, Jedis jedis){ if (null != jedis){ jedisPool.close(); } } }
修改代码,doSecKill方法中通过连接池获取Jedis对象
// 秒杀过程 public static boolean doSecKill(String uid,String prodid) throws IOException{ //1.uid和prodid非空判断 if (uid == null || prodid == null){ return false; } //2.通过连接池得到jedis对象 Jedis jedis = JedisPoolUtil.getJedisPoolInstance().getResource();利用乐观锁淘汰用户,解决超卖问题
// 秒杀过程 public static boolean doSecKill(String uid,String prodid) throws IOException{ //1.uid和prodid非空判断 if (uid == null || prodid == null){ return false; } //2.通过连接池得到jedis对象 Jedis jedis = JedisPoolUtil.getJedisPoolInstance().getResource(); //3.拼接Key //3.1 库存key String kcKey = "sk:"+prodid+":qt"; //3.2 用户key String userKey = "sk:"+prodid+":user"; //监视库存 jedis.watch(kcKey); //4. 获取库存,如果库存为null,秒杀还没又开始 String kc = jedis.get(kcKey); if (kc == null){ System.out.println("秒杀还没有开始请等待"); jedis.close(); return false; } //5. 判断用户是否重复秒杀 *** 作 if(jedis.sismember(userKey,uid)//命令判断成员元素是否是集合的成员 ){ System.out.println("已经成功秒杀"); jedis.close(); return false; } //6 判断如果商品数量,库存数量小于1,秒杀结束 if (Integer.parseInt(kc) < 1){ System.out.println("秒杀已经结束"); jedis.close(); return false; } //7 秒杀过程 // 使用事务 Transaction multi = jedis.multi(); //组队操作 multi.decr(kcKey); multi.sadd(userKey,uid); //执行 List
重新测试,观察控制台输出(太长就不截图了),和redis key的值
库存遗留问题解决在测试中增加库存量
2000个请求300个并发
ab -n 2000 -c 300 -p /home/xm/postfile -T application/x-www-form-urlencoded http://192.168.2.2:8080/doseckill
我们发现库存并没有清零
这是乐观锁造成的库存遗留问题,部分请求并没能成功执行秒杀,因为事务执行时,重新检测库存数量,发现和最初watch检测的库存数量不一致(乐观锁版本号的机制)
为了解决这个问题,我们使用Lua脚本解决这个问题
什么是Lua脚本Lua是一个小巧的脚本语言,Lua脚本可以很容易的被C/C++代码调用,也可以反过来调用C/C++的函数,Lua并没有提供强大的库,一个完整的Lua解释器不过200k ,所以Lua不适合作为开发独立应用程序的语言,而是作为嵌入式脚本语言。
很多应用程序、游戏使用LUA作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。
这其中包括魔兽争霸地图、魔兽世界、博德之门、愤怒的小鸟等众多游戏插件或外挂。
https://www.w3cschool.cn/lual
public class SecKill_redisByscript { static String secKillscript = "local userid=KEYS[1];rn" + "local prodid=KEYS[2];rn" + "local qtkey='sk:'..prodid..":qt";rn" + "local usersKey='sk:'..prodid..":user";rn" + "local userExists=redis.call("sismember",usersKey,userid);rn" + "if tonumber(userExists)==1 then rn" + " return 2;rn" + "endrn" + "local num= redis.call("get" ,qtkey);rn" + "if tonumber(num)<=0 then rn" + " return 0;rn" + "else rn" + " redis.call("decr",qtkey);rn" + " redis.call("sadd",usersKey,userid);rn" + "endrn" + "return 1" ; public static boolean doSkillByscript(String userid,String prodid){ Jedis jedis = JedisPoolUtil.getJedisPoolInstance().getResource(); String sha1 = jedis.scriptLoad(secKillscript); Object result = jedis.evalsha(sha1, 2, userid, prodid); String reString = String.valueOf(result); if ("0".equals( reString ) ) { System.err.println("已抢空!!"); }else if("1".equals( reString ) ) { System.out.println("抢购成功!!!!"); }else if("2".equals( reString ) ) { System.err.println("该用户已抢过!!"); }else{ System.err.println("抢购异常!!"); } jedis.close(); return true; } }
参考:
尚硅谷-Redis 6 入门到精通 超详细 教程
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)