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";
}
}