解决问题:
redis分布式锁可以解决超卖的现象
Spring Schedule解决定时关单的问题
1、redis分布式锁流程图
出现的问题:这种情况的当一个线程拿到了锁,但是还没有释放,(就是将key,value设置到redis里面成功,但是没有删除)这时将tomcat都关闭,以后再启动项目,这个锁就是死锁,永远不会释放了。
解决上面的问题,可以在CloseOrderTask类里面写这方法:
@PreDestroy
public void delLock(){
RedisShardedPoolUtil.del(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
}
但当锁比较多的时候,关闭的时间就非常长,如果直接kill掉tomacat的进程,上面的方法就不会执行,故产生下面升级版的方法解决以上问题
2、redis分布式锁升级版流程图
升级版的就解决了上面的出现死锁的问题。
1、设置一个lockTimeout 的时间,开启后多少时间后进行关单的时间
2、获取分布式锁,将key,value通过setnx设置到redis里面,因为key是固定的,如key在redis里面存在是不能设置的,必须等它被删除后
3、expire设置锁的有效期
备注:当一个线程拿到了锁,就是在redis里面已经设置了,第二线程是拿不到锁的(就是以key,value设置在redis里面),必须等设置的有效期过了(expire),将设置在redis里面的key,value删除掉,第二个线程才能拿到锁(设置key,value在redis里面)
1、redis分布式锁
第一步:在CloseOrderTask类写一个方法
package com.mmall.task;
import com.mmall.common.Const;
import com.mmall.common.RedissonManager;
import com.mmall.service.IOrderService;
import com.mmall.util.PropertiesUtil;
import com.mmall.util.RedisShardedPoolUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class CloseOrderTask {
@Autowired
private IOrderService iOrderService;
@Autowired
private RedissonManager redissonManager;
@PreDestroy
public void delLock(){
RedisShardedPoolUtil.del(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
}
// @Scheduled(cron="0 */1 * * * ?")
public void closeOrderTaskV2(){
log.info("关闭订单定时任务启动");
long lockTimeout = Long.parseLong(PropertiesUtil.getProperty("lock.timeout","5000"));
//key为自定义的字符串,value为当前时间加设置多久关闭的时间
Long setnxResult = RedisShardedPoolUtil.setnx(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,String.valueOf(System.currentTimeMillis()+lockTimeout));
if(setnxResult != null && setnxResult.intValue() == 1){
//如果返回值是1,代表设置成功,获取锁
closeOrder(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
}else{
log.info("没有获得分布式锁:{}",Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
}
log.info("关闭订单定时任务结束");
}
private void closeOrder(String lockName){
//设置锁的有效期
RedisShardedPoolUtil.expire(lockName,5);//有效期50秒,防止死锁
log.info("获取{},ThreadName:{}",Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,Thread.currentThread().getName());
int hour = Integer.parseInt(PropertiesUtil.getProperty("close.order.task.time.hour","2"));
iOrderService.closeOrder(hour);
//释放锁
RedisShardedPoolUtil.del(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
log.info("释放{},ThreadName:{}",Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,Thread.currentThread().getName());
log.info("===============================");
}
}
第二步:在RedisShardedPoolUtil类里面写一个setnx方法(表示如果锁的key不存在,才会设置成功)
public static Long setnx(String key,String value){
ShardedJedis jedis = null;
Long result = null;
try {
jedis = RedisShardedPool.getJedis();
result = jedis.setnx(key,value);
} catch (Exception e) {
log.error("setnx key:{} value:{} error",key,value,e);
RedisShardedPool.returnBrokenResource(jedis);
return result;
}
RedisShardedPool.returnResource(jedis);
return result;
}
2、redis分布式锁升级版(分布式锁双重防死锁)
将时间戳可以利用起来了
如果没有获取到锁,在redis里面进行get锁的key,并判断value的的值和当前时间进行对比,如存在,获取到redis里面旧锁的值,并进行判断,如果为null或从redis里面进行get锁的key得到的值和getset得到的值是一样的话,表示我们真正获取到新锁,并且set到了redis里面。
第一步:在CloseOrderTask类写一个方法
@Scheduled(cron="0 */1 * * * ?")
public void closeOrderTaskV3(){
log.info("关闭订单定时任务启动");
long lockTimeout = Long.parseLong(PropertiesUtil.getProperty("lock.timeout","5000"));
Long setnxResult = RedisShardedPoolUtil.setnx(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,String.valueOf(System.currentTimeMillis()+lockTimeout));
if(setnxResult != null && setnxResult.intValue() == 1){
closeOrder(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
}else{
//未获取到锁,继续判断,判断时间戳,看是否可以重置并获取到锁(重点的地方)
String lockValueStr = RedisShardedPoolUtil.get(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
//表示这个锁已经失效
if(lockValueStr != null && System.currentTimeMillis() > Long.parseLong(lockValueStr)){
//重新设置锁,并拿到旧锁的值,getset具有原子性
String getSetResult = RedisShardedPoolUtil.getSet(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,String.valueOf(System.currentTimeMillis()+lockTimeout));
//再次用当前时间戳getset。
//返回给定的key的旧值,->旧值判断,是否可以获取锁
//当key没有旧值时,即key不存在时,返回nil ->获取锁
//这里我们set了一个新的value值,获取旧的值。
if(getSetResult == null || (getSetResult != null && StringUtils.equals(lockValueStr,getSetResult))){
//真正获取到锁
closeOrder(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
}else{
log.info("没有获取到分布式锁:{}",Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
}
}else{
log.info("没有获取到分布式锁:{}",Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);
}
}
log.info("关闭订单定时任务结束");
}