redis实现秒杀功能-超卖&库存遗留问题的解决

目录

实现一个基础的秒杀逻辑

验证秒杀逻辑

安装ab工具

第一次验证

出现了超卖问题

第二次验证

出现了库存遗留问题

第三次最终验证成功


实现一个基础的秒杀逻辑

1.扣减库存

2.记录秒杀成功人员

java 复制代码
public static boolean doSecKill(String uid, String prodid) {

        // 拼接向Redis中保存库存和秒杀成功人员名单的key
        String kcKey = "sk:" + prodid + ":qt";
        String userKey = "sk:" + prodid + "usr";
        // 创建Jedis对象
        Jedis jedis = new Jedis("8.160.170.127", 6379);
        // 获取库存
        String count = jedis.get(kcKey);
        // 11判断库存的数量
        if ("0".equals(count)) {
            System.err.println("秒杀结束");
            return false;
        }

        // 扣库存
        jedis.decr(kcKey);
        // 添加人员记录
        jedis.sadd(userKey, uid);
        System.out.println(uid + ": 秒杀成功");

        return true;
    }
验证秒杀逻辑

需要使用ab工具模拟并发

安装ab工具

使用如下命令进行安装

yum -y install httpd-tools

安装完成

启动项目

java -jar -d seckill-demo-0.0.1-SNAPSHOT.jar

准备ab测试命令

准备testfile

ab指定文件内容

ab -n 2000 -c 200 -k -p testfile -T application/x-www-form-urlencoded http://localhost:8080/seckill

或

ab -n 2000 -c 200 -k -p testfile -T application/x-www-form-urlencoded http://yourIP:8080/seckill

ab命令参数说明

  • -n 2000:总请求数,一共发送 2000 次请求
  • -c 200:并发数,同时 200 个并发连接(模拟 200 个用户同时访问)
  • -k:开启 HTTP KeepAlive,复用 TCP 连接,减少建立连接开销,更贴近真实浏览器行为
  • -p testfile:POST 请求,从 testfile 文件读取 POST 参数 ,文件内就是表单参数,例如seckillId=1001&userId=123
  • -T application/x-www-form-urlencoded:POST 请求的Content-Type,表单提交格式(和-p配套使用)
  • http://localhost:8080/seckill:压测目标秒杀接口地址
第一次验证

执行完后,查看库存

出现了超卖问题

使用redis事务解决

java 复制代码
public static boolean doSecKill2(String uid, String prodid) {

        // 拼接向Redis中保存库存和秒杀成功人员名单的key
        String kcKey = "sk:" + prodid + ":qt";
        String userKey = "sk:" + prodid + ":usr";
        // 创建Jedis对象
        Jedis jedis = new Jedis("8.160.170.127", 6379);
        // // 获取库存
        // String count = jedis.get(kcKey);
        //增加乐观锁
        jedis.watch(kcKey);
        //3.判断库存
        String kcKeystr = jedis.get(kcKey);
        if (kcKeystr == null || "".equals(kcKeystr.trim())) {
            System.out.println("未初始化库存");
            jedis.close();
            return false;
        }

        int kc = Integer.parseInt(kcKeystr);
        if (kc <= 0) {
            System.err.println("已经秒光。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。");
            jedis.close();
            return false;
        }

        //增加事务
        Transaction multi = jedis.multi();
        // 扣库存
        // jedis.decr(kcKey);
        multi.decr(kcKey);
        // 添加人员记录
        // jedis.sadd(userKey, uid);
        multi.sadd(userKey, uid);


        //执行事务
        List<Object> list = multi.exec();
        //判断事务提交是否失败
        if (list == null || list.size() == 0) {
            System.out.println("秒杀失败");
            jedis.close();
            return false;
        }

        System.out.println(uid + ": 秒杀成功");
        jedis.close();
        return true;
    }

设置库存为100

第二次验证

重新使用ab模拟并发

查看库存结果

出现了库存遗留问题

使用lua脚本解决

lua脚本

java 复制代码
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..\":usr\";\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";

业务代码

java 复制代码
public static boolean doSecKill(String uid, String prodid) {

        JedisPool jedispool = JedisPoolUtil.getJedisPoolInstance();
        Jedis jedis = jedispool.getResource();

        String sha1 = jedis.scriptLoad(secKillScript);
        Object result = jedis.evalsha(sha1, 2, uid, 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;
    }
第三次最终验证成功

库存恢复100,再次使用ab调用,查看redis库存,正常了

相关推荐
逐流人1 小时前
Redis内存数据库:从数据类型到集群部署
linux·运维·数据库·redis·缓存·云计算
l1t1 小时前
DeepSeek总结的在 NVIDIA Vera 上对 DuckDB v2.0-dev 进行基准测试
服务器·数据库·sql
渣渣盟1 小时前
2000万QPS背后的存储引擎博弈:InnoDB、MyRocks、WiredTiger到底谁更强?
数据库·mysql
ss2731 小时前
AI时代首先数据库-PostgreSQL实战笔记 | 11 PostgreSQL安装部署常见问题排查与解决
数据库·笔记·postgresql
张张123y2 小时前
# 后端第八课:数据库入门(给 FastAPI 接口加上“记忆“,存下问答历史)
数据库·sql·mysql·oracle·fastapi·sqllite
cspttty2 小时前
2027秋招市场调研岗:统计学专业如何补齐业务、SQL与研究交付能力
大数据·数据库
BD_Marathon2 小时前
Milvus数据库的DML操作
数据库·milvus
其实防守也摸鱼2 小时前
存储管理:页面置换算法学习任务(测验题要求给出置换页面顺序,缺页次数,缺页率)
android·前端·数据库·学习·oracle·自动化
cspttty2 小时前
2027校招:大数据管理与应用专业如何匹配电商运营与供应链管培生能力要求
大数据·数据库