redis模拟秒杀解决超卖问题(二)

在第一次的版本中,使用redis模拟秒杀,最终结果,单个线程可以执行,使用了阿帕奇的ab工具,进行了压力测试后,出现了超卖问题,本代码中,针对此问题进行解决。
在redis中,提供了事务的概念,redis的事务在执行过程中,不会被打断,multi开启事务,在此之后的才做将被添加至操作队列中,如图
在这里插入图片描述
添加完成后,可以使用exec进行执行
在这里插入图片描述
如果想放弃,则可以使用discard取消执行的事务
在这里插入图片描述
要想完成秒杀超卖的问题,当然还有一个非常重要的点,redis的watch方法,它是一个乐观锁的命令,会未监控的key增加版本号,在事务执行前,监控key,如果执行中,key的版本号发生变化,则事务取消,不会执行


import com.lixl.redis.utils.RandomUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @author lixl
 * @description 秒杀相关controller
 * @date 2022/2/10
 */
@RestController
public class SecKillController {

    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping("doSecKill")
    public String doSeckill(String goodId){
        String userId = RandomUtils.getUserId()+"";
        if (!StringUtils.hasText(goodId)){
            System.out.println("没有该类商品的秒杀");
            return "没有该类商品的秒杀";
        }
        // 拼接库存key
        String kcKey = "good:"+goodId+":kc";
        // 拼接秒杀成功用户key
        String userKey = "good:"+goodId+":user";
        // 判断是否有库存
        Boolean hasKey = redisTemplate.hasKey(kcKey);
        if (!hasKey){
            System.out.println("没有该类商品的秒杀");
            return "没有该类商品的秒杀";
        }
        // 判断用户是否已经完成过秒杀
        Boolean isMember = redisTemplate.opsForSet().isMember(userKey, userId);
        if (isMember){
            System.out.println("您已经秒杀成功,请勿重复秒杀!");
            return "您已经秒杀成功,请勿重复秒杀!";
        }
        // 开启redis的事务,使用redisTemplate开启事务,需要用到SessionCallback,具体事务在它的模块中实现
        SessionCallback sessionCallback = new SessionCallback() {
            @Override
            public Object execute(RedisOperations redisOperations) throws DataAccessException {
                // 使用redis提供的监控数量功能(乐观锁,给每个数据一个版本号,不符合版本号的,无法执行)
                redisOperations.watch(kcKey);
                // 判断库存数量是否够
                String goodsNum = redisOperations.opsForValue().get(kcKey).toString();
                System.out.println(goodsNum);
                if (Integer.parseInt(goodsNum)<=0){
                    return "秒杀已经结束,抢购失败!";
                }
                redisOperations.multi();
                // 库存数量-1
                redisOperations.opsForValue().decrement(kcKey);
                // 设置用户
                redisOperations.opsForSet().add(userKey,userId);
                // 执行事务
                List exec = redisOperations.exec();
                if (exec == null || exec.size() == 0){
                    return "很遗憾,您未秒杀到该商品";
                } else {
                    return "恭喜您,秒杀成功!";
                }
            }
        };
        // 执行事务
        Object execute = redisTemplate.execute(sessionCallback);
        System.out.println(execute.toString());
        return execute.toString();
    }

}

使用ab工具
ab -n 1000 -c 100 http://localhost:8080/doSecKill?goodId=1001
发送1000次请求 并发100
在这里插入图片描述
可以看到,最后只有0个,并未出现超卖问题,但是在情况下,如果设置多一点的库存,多一点的并发,又会怎样(500库存,1000请求,500并发)
在这里插入图片描述
在这里插入图片描述
此时可以看到,还有342个商品,未被秒杀到,还需要接着完善代码
在这里插入图片描述

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值