Redis 秒杀案例

文章目录

  • Redis 秒杀案例
    • 实现
    • ab工具模拟并发
    • 超卖和超时问题解决
      • 配置JedisPool连接池来解决超时问题
      • 利用乐观锁淘汰用户,解决超卖问题
    • 库存遗留问题解决
      • 什么是Lua脚本
      • Lua脚本在redis中的优势
      • 编写Lua脚本

实现

写一个简单的springboot + thymeleaf页面示例

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<base th:href="@{/}"/>
<body>
<h1>iPhone 13 Pro !!! 1元秒杀
</h1>
<form id="msfrom"><input type="hidden" id="prodid" name="prodid" value="0101"><input type="button" id="miaosha_btn" name="seckill_btn" value="秒杀点我">
</form>
</body>
<script type="text/javascript" src="jquery/jquery-3.5.1.js"></script>
<script type="text/javascript">$(function () {$("#miaosha_btn").click(function () {var prodid = $("#prodid").val();$.ajax({url: "http://localhost:8080/doseckill",type:"post",data: {"prodid":prodid},dataType: "json",success:function (data){if (data === "false"){alert("抢光了");$("#miaosha_btn").attr("disabled",true);}},error:function (resp) {}})})})
</script>
</html>

controller

    @PostMapping("/doseckill")@ResponseBodypublic 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.连接redisJedis jedis = new Jedis("192.168.0.2",6379);jedis.auth("password");//3.拼接Key//3.1 库存keyString kcKey = "sk:"+prodid+":qt";//3.2 用户keyString 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 库存-1jedis.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 库存keyString kcKey = "sk:"+prodid+":qt";//3.2 用户keyString 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<Object> results = multi.exec();if (results == null || results.size()==0){System.out.println("秒杀失败了");jedis.close();}System.out.println("秒杀成功");jedis.close();return true;}

重新测试,观察控制台输出(太长就不截图了),和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脚本

  1. Lua是一个小巧的脚本语言,Lua脚本可以很容易的被C/C++代码调用,也可以反过来调用C/C++的函数,Lua并没有提供强大的库,一个完整的Lua解释器不过200k ,所以Lua不适合作为开发独立应用程序的语言,而是作为嵌入式脚本语言。

  2. 很多应用程序、游戏使用LUA作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。

  3. 这其中包括魔兽争霸地图、魔兽世界、博德之门、愤怒的小鸟等众多游戏插件或外挂。

    https://www.w3cschool.cn/lual

Lua脚本在redis中的优势

编写Lua脚本

public class SecKill_redisByScript {static String secKillScript ="local userid=KEYS[1];\r\n" +"local prodid=KEYS[2];\r\n" +"local qtkey='sk:'..prodid..\":qt\";\r\n" +"local usersKey='sk:'..prodid..\":user\";\r\n" +"local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" +"if tonumber(userExists)==1 then \r\n" +"   return 2;\r\n" +"end\r\n" +"local num= redis.call(\"get\" ,qtkey);\r\n" +"if tonumber(num)<=0 then \r\n" +"   return 0;\r\n" +"else \r\n" +"   redis.call(\"decr\",qtkey);\r\n" +"   redis.call(\"sadd\",usersKey,userid);\r\n" +"end\r\n" +"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 入门到精通 超详细 教程

Redis 秒杀案例相关推荐

  1. 黑猴子的家:LUA脚本(Redis 秒杀案例)

    Code -> GigHub https://github.com/liufengji/redis_seckill.git 1.概念 http://www.lua.org/download.ht ...

  2. 保姆级redis6全流程学习和redis秒杀全流程

    文章目录 前言 一.redis解决的问题 1.1.1解决session共享问题 1.1.2降低io读操作 1.2.1nosql数据库的特点 1.2.2 NoSQL的适用场景 1.2.3 NoSQL不适 ...

  3. NetCore3.1连接Redis做秒杀案例

    测试环境:netcore3.1   redis-6.2.4 一:安装Redis 尽管在不是系统性介绍Radis的地方介绍安装radis并不是一件明智的事情,但本着能跑起来就算成功的原则,这里简单介绍一 ...

  4. Redis学习之秒杀案例

    记录每次学习的过程,总结学习的内容,希望能给到自己和别人帮助. Redis学习之秒杀案例 基础设计思路 1.uid和prodid的非空判断 2.连接redis 3.拼接key,包括库存的和成功秒杀的用 ...

  5. Redis | 26.Redis事务案例 - 秒杀 - 库存遗留问题

    文章目录 0.前言 1.何为库存遗留问题? 2.解决方案 0.前言 参考视频:27-尚硅谷-Redis6-事务和锁机制-秒杀案例-库存遗留问题 1.何为库存遗留问题? 假设商品库存100件,在上一期使 ...

  6. 尚硅谷Redis6基础教程-秒杀案例中库存遗留问题

    尚硅谷redis6基础教程中视频24-27的秒杀案例,使用Redis乐观锁解决了超卖问题,但是也产生了库存遗留问题.引入Lua脚本,解决了超卖和库存遗留.Lua脚本为什么解决了库存遗留问题???

  7. 11、Redis_事务_秒杀案例

    11. Redis_事务_秒杀案例 11.1. 解决计数器和人员记录的事务操作 基本功能实现: public class SecKillController {//秒杀过程public static ...

  8. Redis简单案例(二) 网站最近的访问用户

    原文:Redis简单案例(二) 网站最近的访问用户 我们有时会在网站中看到最后的访问用户.最近的活跃用户等等诸如此类的一些信息.本文就以最后的访问用户为例, 用Redis来实现这个小功能.在这之前,我 ...

  9. Redis简单案例(四) Session的管理

    Redis简单案例(四) Session的管理 原文:Redis简单案例(四) Session的管理 负载均衡,这应该是一个永恒的话题,也是一个十分重要的话题.毕竟当网站成长到一定程度,访问量自然也是 ...

最新文章

  1. 10 ping不通widwos7 windwos_弱电老司机总结的10种视频监控系统故障解决方法,学会,事半功倍...
  2. 使用JNA,让java调用原生代码
  3. 儿子转眼就长大:Hinton、LeCun、Bengio 口述神经网络简史
  4. shell整理(41)====判断输入是不是ip
  5. python引入redis_redis 删除大key集合的方法
  6. java enumerable_java - Java相当于C#的'Enumerable.Any' - 堆栈内存溢出
  7. SORT,DELETE ADJACEN DUPLICATES FROM保留有效数据
  8. k8s:组件网络通讯方式
  9. 开发中的“软”与“硬”:高画质移动游戏开发之道
  10. Linux内核(5) - 内核学习的相关资源
  11. 启动u盘自动运行服务器,WinPE网启服务器自动配置程序
  12. day04-商城后台搭建
  13. 语音转写(讯飞开放平台)工具类
  14. 短信API接口怎么调用
  15. VUE使用benz-amr-recorder 实现解码、播放
  16. (二)Gluster 架构部分(节-1)
  17. 在虚拟机上安装gho、esd(wim)系统镜像文件
  18. 用vue写轮子的一些心得(五)——Slides轮播组件
  19. 教你怎么批量查询快递单号,一学就会
  20. c语言open()介绍

热门文章

  1. 程序员学英语:口语999句之(667-999句)
  2. 20180102189刘钰
  3. [剑指 offer] -- 动态规划-- 面试题46. 把数字翻译成字符串
  4. 【原创】技术员 Ghost Win7 Sp1 x64 纯净版 2017
  5. C#读Visio模型数据
  6. 吐血整理 | 据说这里有一份关于BAT的 “宝藏级” 面试记录终于可以看了!
  7. R语言和医学统计学(2):方差分析
  8. 2/3企业遭受ERP数据泄露,服务器系统数据该如何保护?
  9. 应用再设计__Thanx
  10. 【莹伙丛】Dependencies should no longer be declared using the compile and runtime configurations