redis分布式锁的使用

Posted shouyaya

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis分布式锁的使用相关的知识,希望对你有一定的参考价值。

1.通过官方文档查看相关的命令操作:http://www.redis.cn/commands.html

  关键命令:setIfAbsent(String key,String value)对应官方文档的 setnx

       解释:将key设置值为value,如果key不存在,这种情况下等同SET命令。 当key存在时,什么也不做。SETNX是”SET if Not eXists”的简写。

      getAndSet

      解释:自动将key对应到value并且返回原来key对应的value。如果key存在但是对应的value不是字符串,就返回错误。(get旧值,set新值)

2.编写redisLock,即上锁和解锁的方法:

@Service
@Slf4j
public class RedisLock {
    @Autowired
    StringRedisTemplate template;

    public boolean lock(String key,String value){
        //1.如果能设置value表示锁没被占用,返回true表示上锁成功,否则进入下一步
        if (template.opsForValue().setIfAbsent(key,value)) {
            return true;
        }
        
        String currentValue = template.opsForValue().get(key);
        //2.这一步是为了防止死锁,因为如果被上锁的那段程序抛出异常,会导致无法执行解锁方法从而导致死锁
        //所以value的值应设为当前时间+超时时间,当Redis里的currentValue小于线程当前的系统时间,
        //说明死锁产生了,需解开这个死锁
        if(!StringUtils.isEmpty(currentValue)&&Long.valueOf(currentValue)<System.currentTimeMillis()) {
            //3.解死锁的操作,把原来的currentValue设置为新的value,并为拿到锁的新线程重新上锁
            String oldValue = template.opsForValue().getAndSet(key, value);
            //4.这步是为了防止在死锁产生的时候,同时有多个线程进来上锁
            // ,只有oldValue等于currentValue的线程才可成功上锁
            if(!StringUtils.isEmpty(oldValue)&&oldValue.equals(currentValue)) {
                template.opsForValue().set(key,value);
                return true;
            }
        }
        //上锁失败
        return false;
    }

    public void unlock(String key,String value){
        
        try {
            String currentValue = template.opsForValue().get(key);
            //只有在Redis里面的CurrentValue等于传进来的Value才能解锁
            if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
                template.delete(key);
            }
        }catch (Exception e){
            log.error("【redis分布式锁】解锁异常, {}", e);
        }
    }
}

3.在相应的程序里调用redisLock

 @Override
    public String orderSecKillProduct(String productId) {
        //value的值设为当前时间+超时时间
        String time=String.valueOf(System.currentTimeMillis()+10*1000);
        //加锁
        if(!redisLock.lock(productId,time)){
            //上锁失败,抛出异常跳转到相应的页码
            throw  new SellException(101,"太多人抢购了,请重试");
        }
        //查询库存若为0则,提示已抢购完
        Integer stockNum = productStock.get(productId);
        if(stockNum==0){
            throw new SellException(100,"已被抢购完了");
        }
        //下单
        ordered.put(KeyUtil.genUniqueKey(), productId);
        //减库存
        stockNum=stockNum-1;
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //存入数据库
        productStock.put(productId,stockNum);
        //解锁
        redisLock.unlock(productId,time);
        //查询
        return query(productId);
    }

 

 

setIfAbsent

以上是关于redis分布式锁的使用的主要内容,如果未能解决你的问题,请参考以下文章

Redis 分布式锁的作用及实现(序列四)

Redis进阶学习03---Redis完成秒杀和Redis分布式锁的应用

80% 人不知道的 Redis 分布式锁的正确实现方式(Java 版)

redis分布式锁的问题和解决

Redis 分布式锁的正确实现原理演化历程与 Redisson 实战总结

Redis分布式锁的实现方式