1 设置秒杀商品
提供一个接口给前端,当有秒杀商品上架的时候,完善好商品的信息之后,设置为秒杀商品,设置秒杀开始时间,通过DelayQueuq延迟队列,定时上架,可以秒杀,在redis中set一个key,key值为商品id+Flag,例如‘10000Flag’,值为0,表示未开始秒杀,防止被用户恶意提前开始抢购。
redis代码
redisTemplate.opsForValue().set("10000Flag","0");
redis中再放置一个key/list 数据,key为商品id,list中的值为商品id,list的长度为商品设置的秒杀数量,每一个list中的值称为令牌,也就是秒杀的门票。
int goodsNum = 10;
for(int i = 0; i < goodsNum; i++){
redisTemplate.opsForList().leftPush("10000","10000");
}
2 秒杀接口
先要判断是否可以开始秒杀了,一个用户只能秒杀一次,所以要判断用户是否已经秒杀过,通过redis给用户设置一把分布式锁,用setIfAbesent设置,key为商品id+用户id,值为当前时间戳,还未生成订单,1为生成订单,2为已完成,生成订单后,这把锁的获取时间就没有用了。
//2.0操作
String flag = redisTemplate.opsForValue().get("10000");
if("0".equals(flag)){
//还不可以开始秒杀
return;
}
//2.1操作
Boolean lockFlag = redisTemplate.opsForValue().setIfAbsent("goodId"+"userId", System.currentTimeMillis()+"",600, TimeUnit.SECONDS);
if(lockFlag){
//获得锁 可以继续 判断商品数量
}
当用户拿到锁了,再判断秒杀商品的数量
//2.2操作
String good = redisTemplate.opsForList().leftPop("10000");
if(good != null){
//说明还有商品 可以秒杀 否则返回秒杀商品已售完
//2.3 生成订单 订单中包含商品id和用户id 去支付
//订单生成完毕
redisTemplate.opsForValue().set("商品id"+"用户id","1");
}
3 用户取消支付
定时任务判断用户支付状态,当用户超过支付截止时间+30s(30s为了预防网络延迟因素)未支付,则设置状态为取消,此时通过订单记录里的商品id和用户id,删除掉用户的锁
redisTemplate.delete("商品id"+"用户id");
并且将取得的list中的令牌还回去。其他用户可以继续秒杀。
redisTemplate.opsForList().leftPush("商品id","商品id");
4 支付完成
redisTemplate.opsForValue().set("商品id"+"用户id","2");
5 清理redis数据
定时任务去清理,秒杀第二天0点的时候去批量删除数据
//删除商品是否开始秒杀标签
redisTemplate.delete("商品id"+"Flag");
//删除商品数量
redisTemplate.delete("商品id");
6 问题
当程序在2.3用户创建订单之前,获取锁之后挂了,这时候这个用户又进来了,但是锁已经存在了,怎么处理,还得保证上一次用户获取这个锁的时间间隔不能太短。
//2.1操作
Boolean lockFlag = redisTemplate.opsForValue().setIfAbsent("goodId"+"userId","0",600, TimeUnit.SECONDS);
if(!lockFlag){
//没获得锁
String timeValue = redisTemplate.opsForValue().get("goodId"+"userId");
// timeValue.length() 说明不是1和2 还未生成订单
if(timeValue.length() > 1){
Long oldTime = Long.parseLong(timeValue);
if(oldTime - System.currentTimeMillis() < 5000){
//距离第一次生成分布式锁过去不足5s返回系统繁忙,请等待
return;
}
}
//根据商品id+用户id 查询订单
//如果存在 直接返回
if(timeValue.length() > 1 || hasOrder){
return;
}
//不存在 加锁 加在类上
synchronized(Demo.class){
//再判断是否有订单 有直接返回 没有继续。
if(timeValue.length() > 1 || hasOrder){
return;
}
}
}
//如果没有提前结束 就表明这个用户没有订单,用户经过登陆验证,认为是可靠的,这个分布式锁是可重入的,可以继续由这个用户使用。
如果2.2操作 即获取商品令牌之后,生成订单之前挂了,用户再次进来,不应该再去减库存,应该直接去生成订单的操作,所以当商品令牌扣减之后,讲用户和令牌绑定在一起。
//2.2操作
String good = redisTemplate.opsForValue().get("goodId"+"userId");
//是否需要拿商品令牌
Boolean flag = true;
if( "商品id".equals(good)){
//说明还未生成订单,但是商品的令牌已经拿到了,根据用户id和商品id去查询
if(hasOrder){
return;
}
synchronized(Demo.class){
if(hasOrder || good.length == 1){
return;
}else{
flag = false;
}
}
}
if(flag){
good = redisTemplate.opsForList().leftPop("10000");
if(good == null){
//秒杀商品已售完。
return;
}
}
//说明还有商品 可以秒杀 否则返回秒杀商品已售完
redisTemplate.opsForValue().set("商品id"+"用户id", good);
//2.3 生成订单 订单中包含商品id和用户id 去支付
//订单生成完毕
redisTemplate.opsForValue().set("商品id"+"用户id","1");