当前位置:网站首页>Redis秒杀demo

Redis秒杀demo

2022-07-01 15:24:00 y_w_x_k

方案一:方法上加上synchronized即可,线程安全方案,但是执行效率较低,不适合大型并发场合

方案二:采用乐观锁 

controller:

@RestController
public class SecondKill {

    @Autowired
    SecondKillSev secondKillSev;

    @RequestMapping(value = "/buy",method = RequestMethod.POST)
    public Mes buy(@RequestParam("proid") String proid){
        String usedId=new Random().nextInt(1000)+"";
        Mes mes = secondKillSev.doSecondKill(usedId, proid);
        return mes;
    }
}

service:

@Service
public class SecondKillSev {

    Mes message;

    public SecondKillSev(){

    }

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    /**
     * 执行秒杀
     * 方案一:synchronized,线程安全,但是执行效率较低,不适合大型并发场合
     * 方案二:用乐观锁监听事务
     */
    public Mes doSecondKill(String uid, String proid){

        //1.判断uid和proid非空
        if(uid==null || proid==null){
            return Mes.getMes("400","uid和proid不能为空");
        }

        //2.连接redis
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();

        //3.拼接key,库存key,成功用户key
        String kcKey="kcKey:"+proid;
        String userKey="userKey:"+proid;

        //乐观锁解决超卖问题
        //stringRedisTemplate默认不开启事物
        stringRedisTemplate.setEnableTransactionSupport(true);

        List<Object> txResults = stringRedisTemplate.execute(new SessionCallback<List<Object>>() {
            public List<Object> execute(RedisOperations operations) throws DataAccessException {
                //监视库存
                operations.watch(kcKey);
                //4.获取库存,如果库存是null,则秒杀还没开始
                String kc=ops.get(kcKey);
                if(kc==null){
                    message= Mes.getMes("400","库存是null,秒杀还没开始");
                    return null;
                }

                //5.判断用户是否重复秒杀
                if(operations.opsForSet().isMember(userKey, uid)){
                    message=Mes.getMes("400","不能重复秒杀");
                    return null;
                }

                //6.判断商品数量,如果数量小于1,则秒杀结束
                if(Integer.parseInt(kc)<=0){
                    message=Mes.getMes("400","秒杀已经结束");
                    return null;
                }

                //7.秒杀过程,库存-1,成功用户添加到清单
                //使用事务
                operations.multi();
                operations.opsForValue().decrement(kcKey);
                operations.opsForSet().add(userKey,uid);

                List exec = operations.exec();
                if(exec==null || exec.size()==0){
                    message=Mes.getMes("400","秒杀失败");
                    return null;
                }else{
                    return exec;
                }
            }
        });

        if(txResults==null ||txResults.size()==0){
            return message;
        }

        return Mes.getMes("200","秒杀成功");
    }
}

但是这种方案也有一个缺点,就是如果商品的数量是500,但是线程数是1000,即访问量不是非常多的情况下,会出现库存没有卖完的问题,即库存遗留问题,所以可以用lua脚本来解决;

方案三:lua脚本

local userid=KEYS[1];
local prodid=KEYS[2];
local qtkey="sk:"..prodid..":qt";
local userskey="sk:"..prodid..":user";
local userExists=redis.call("sismember",userskey,userid);
if tonumber(userExists)==1 then
  return 2;
end
local num=redis.call("get",qtkey);
if tonumber(num)<=0 then
  return 0;
else
  redis.call("decr",qtkey);
  redis.call("sadd",userskey,userid);
  end
  return 1

代码实现:

@Service
public class SeckillByScript {
    static String secKillScript1 = "local userid=KEYS[1];\n" +
            "local prodid=KEYS[2];\n" +
            "local qtkey=\"sk:\"..prodid..\":qt\";\n" +
            "local userskey=\"sk:\"..prodid..\":user\";\n" +
            "local userExists=redis.call(\"sismember\",userskey,userid);" +
            "if tonumber(userExists)==1 then\n" +
            "  return 2;\n" +
            "end\n" +
            "local num=redis.call(\"get\",qtkey);\n" +
            "if tonumber(num)<=0 then\n" +
            "  return 0;\n" +
            "else\n" +
            "  redis.call(\"decr\",qtkey);\n" +
            "  redis.call(\"sadd\",userskey,userid);\n" +
            "  end\n" +
            "  return 1";

    public boolean doSecKill(String userid, String prodid) {
        JedisPool jedisPool = JedisPoolUtil.getJedisPool();
        Jedis jedis = jedisPool.getResource();
        String sha1 = jedis.scriptLoad(secKillScript1);
        Object result = jedis.evalsha(sha1, 2, userid, prodid);
        String reString = String.valueOf(result);
        if ("0".equals(reString)) {
            System.out.println("已抢空!");
            return false;
        } else if ("1".equals(reString)) {
            System.out.println("抢购成功");
            return true;
        } else if ("2".equals(reString)) {
            System.out.println("该用户已抢过!");
            return false;
        } else {
            System.out.println("抢购异常");
            return false;
        }
    }
}

原网站

版权声明
本文为[y_w_x_k]所创,转载请带上原文链接,感谢
https://blog.csdn.net/y_w_x_k/article/details/125467597