一、核心api
redis做分布式锁主要使用到setnx(set if not exist)如果key不存在则设置成功返回true否则返回false。
二、具体实现
初步实现
@Autowired
private StringRedisTemplate redisLock;
public void business() {
Boolean lock = redisLock.opsForValue().setIfAbsent("key", "value");
if (Boolean.FALSE.equals(lock)) {
log.info("business -> 锁竞争失败!");
return;
}
// 竞争锁成功
// 开始业务处理
log.info("business -> 处理业务。。。");
// 结束业务处理
// 释放锁
redisLock.delete("key");
}
如你所视,上面的代码到处都是破绽。存在问题: 1、处理业务部分报错会导致锁无法释放; 2、拿到锁之后服务宕机会导致锁无法释放; 我们来改造代码尝试解决这两个问题。
@Autowired
private StringRedisTemplate redisLock;
public void business() {
Boolean lock = redisLock.opsForValue().setIfAbsent("key", "value");
if (Boolean.FALSE.equals(lock)) {
log.info("business -> 锁竞争失败!");
return;
}
// 竞争锁成功
try {
// 锁设置过期时间
redisLock.expire("key", 3, TimeUnit.SECONDS);
// 开始业务处理
log.info("business -> 开始处理业务。。。");
// 结束业务处理
} catch (Exception e) {
log.error("business -> 业务处理报错。错误信息:{}", e.getMessage());
} finally {
// 释放锁
redisLock.delete("key");
}
}
通过try-catch-finally解决业务部分报错无法释放锁问题;通过设置过期时间解决拿到锁之后服务宕机问题;
解决了问题又貌似没有完全解决,我们再来分析分析新的问题 1、拿锁和设置过期时间不是原子操作依然存在宕机锁释放不了的问题; 2、如果业务还没有执行完就释放了锁,下个线程拿到的锁有可能会被当前线程释放; 接着改造代码。。。
@Autowired
private StringRedisTemplate redisLock;
public void business() {
String uuid = UUID.randomUUID().toString().replace("-", "");
// 拿锁时同时设置过期时间并且value设置为有区分度的值
Boolean lock = redisLock.opsForValue().setIfAbsent("key", uuid, 3, TimeUnit.SECONDS);
if (Boolean.FALSE.equals(lock)) {
log.info("business -> 锁竞争失败!");
return;
}
// 竞争锁成功
try {
// 开始业务处理
log.info("business -> 开始处理业务。。。");
// 结束业务处理
} catch (Exception e) {
log.error("business -> 业务处理报错。错误信息:{}", e.getMessage());
} finally {
// 判断是否为自己创建的锁并释放锁
if (uuid.equals(redisLock.opsForValue().get("key"))) {
redisLock.delete("key");
}
}
}
拿锁时同时设置过期时间解决了宕机锁不释放问题; value设置为有区分度的值,释放锁的时候判断是否为自己的锁貌似解决了锁释放错的问题;
我们仔细分析一下,貌似是同样的配方同样的问题。 判断是否为自己的锁和释放锁不是原子操作依然存在锁释放错的问题。但是比改造之前几率已经小很多了。 造成这个问题的源头在于我们不能很好的控制锁的过期时间。时间设置太短可能会释放错锁,时间设置太长万一拿到锁服务宕机了。。。一切又回到了原点
想用redis实现一把完美的分布式锁好像不太容易,首先需要解决判断是否为自己的锁和释放锁的原子性和过期时间问题。。。但是。。额,有没有一种可能有个东西叫Redission!