Redisson实现分布式锁

一:分布式作用,为什么使用?

如果我们一个电商网站,只剩一件商品可卖,此时用户A进来后,下单付款,扣减库存,在用户A付款的这个过程中,恰好用户B进来了,也看到还有一个库存,用户B也开始下单付款,扣减库存,那么这个过程容易出现超卖的问题。

而使用分布式锁,用户B必须得等到用户A下单付款,扣减库存后,才可以进行下单操作,从而避免超卖问题。

二:传统Redis实现的分布式锁

(1)基本锁

  • 原理:利用redis的setnx,如果不存在某个key则设置值,设置成功则表示取得锁成功。

  • 缺点:如果获取锁后的进程在没有执行完就挂了,则锁永远不会释放,产生死锁。

(2)改进型

  • 改进:在基本锁上setnx后设置expire,保证超时后也能自动释放锁。

  • 缺点:setnx与expire不是一个原子操作,可能执行完setnx,还没来得及执行expire设置过期时间,该进程就挂了。

(3)增强版

  • 改进:利用Lua脚本,将setnx与expire变成一个原子操作,可解决一部分问题。

  • 缺点:还是锁过期问题。注:2.6.12开始set支持NX、PX

public Boolean lock(String key, Long waitTime, Long expireTime) {
        String value = UUID.randomUUID().toString().replaceAll("-", "").toLowerCase();
        Boolean flag = setNx(key, value, expireTime, TimeUnit.SECONDS);

        if (flag) {  // 尝试获取锁成功返回
            return flag;
        } else { // 获取失败
            // 现在时间
            long newTime = System.currentTimeMillis();
            // 等待过期时间
            long loseTime = newTime + waitTime;
            // 不断尝试获取锁成功返回
            while (System.currentTimeMillis() < loseTime) {
                Boolean testFlag = setNx(key, value, expireTime, TimeUnit.MILLISECONDS);
                if (testFlag) {
                    return testFlag;
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {

                }
            }
        }
        return false;
    }

 

思路:首先尝试获取锁,获取到后,设置失效时间。获取不到,继续等待。

问题:失效时间1分钟,业务操作执行了2分钟,锁失效后,上一个线程还没处理完,下一个线程便拿锁。

处理方法:使用Redisson

三:Redisson分布式锁

1、首先引入maven

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.11.6</version>
</dependency>

构建Redisson实例

@Configuration
public class RedissonConfig {
	
    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private String port;

    @Bean
	public RedissonClient redissonClient(){
       Config config = new Config();
       config.useSingleServer().setAddress("redis://"+host+":"+port);
       return Redisson.create(config);
	}
}
RedissonController 使用
@RestController
public class RedissonController {

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private RedissonClient redissonClient;

    @RequestMapping("/redisson")
     public String TestRedisson(){
         Jedis jedis = redisUtil.getJedis();
         RLock lock = redissonClient.getLock("lock");//声明锁
         lock.lock();//上锁
         String value = jedis.get("redisson-key");
         if(StringUtils.isEmpty(value)){
             value="1";
         }
         jedis.set("redissonKey",(Integer.parseInt(value)+1)+"");
         jedis.close();
         lock.unlock();//解锁
         return "success";
    }
}

 

注:不使用 lock(long leaseTime, TimeUnit unit) 设置过期时间的,因为并不会去检查任务是否执行结束

如果任务还没执行结束,然后锁的过期时间到了,线程中断,就会出现异常。

lock()方法不产传递任何参数,会去校验当前任务是否执行结束,如果没有执行结束,那么相应的就会延长锁的过期时间

 

 

 

 

 

 

 

 

 

 

@RestController
public class RedissonController {

@Resource
private RedisUtil redisUtil;

@Resource
private RedissonClient redissonClient;

@RequestMapping("/redisson")
public String TestRedisson(){
Jedis jedis = redisUtil.getJedis();
RLock lock = redissonClient.getLock("lock");//声明锁
lock.lock();//上锁
String value = jedis.get("redisson-key");
if(StringUtils.isEmpty(value)){
value="1";
}
jedis.set("redissonKey",(Integer.parseInt(value)+1)+"");
jedis.close();
lock.unlock();//解锁
return "success";
}
}
posted @ 2019-12-18 17:03  馥郁  阅读(1313)  评论(0编辑  收藏  举报