1、redis分布式锁如何实现 使用redis的setnx,结果成功就代表加锁成功,失败就代表加锁失败
2、redis分布式锁可能遇到的问题
//加锁
redis.setNx(key,value);
System.out.println("执行业务逻辑");
// 释放锁
redis.delete(key);
上面实现了加锁,但是在业务中如果出现异常,将会导致加锁失败,所以应该在finally语句块中释放锁
try {
//加锁
redis.setNx(key,value);
System.out.println("执行业务逻辑");
} finally {
// 释放锁
redis.delete(key);
}
上面会确保程序在发生异常时也能正常释放锁,但是如果如果加锁后,服务器宕机了,此时还没释放锁,一样会造成锁无法释放,应该给锁设置超时时间
try {
//加锁
redis.setNx(key,value);
redis.expire(key,10, TimeUnit.SECONDS);
System.out.println("执行业务逻辑");
} finally {
// 释放锁
redis.delete(key);
}
上面虽然设置了超时时间,但是如果刚加上锁还没设置超时时间,服务器就宕机了,依然会导致锁无法释放 ,应该保证上锁和设置超时时间的原子性,
try {
//加锁
redis.setNx(key,value,10, TimeUnit.SECONDS);
System.out.println("执行业务逻辑");
} finally {
// 释放锁
redis.delete(key);
}
上面保证了原子性,假设现在有两个线程A,B,线程A加锁成功后已经10s了,此时A业务逻辑还没有执行完毕,这时候锁过期了,线程B获得此时进行加锁,可以正常加锁,但是线程A执行完业务逻辑后,执行了释放锁的逻辑,将B线程持有的锁删除了。那么其他线程又能持有这把锁,这样一来,锁并不能控制并发。所以,应该在删除锁前,应该删除这把锁的线程是不是给这把锁上锁的线程。
try {
//加锁
value = UUID.randomUUID();
redis.setNx(key, value, 10, TimeUnit.SECONDS);
System.out.println("执行业务逻辑");
} finally {
// 释放锁
if (redis.get(key) == value) {
redis.delete(key);
}
}
上面在删除前做了一个判断,只有value的值是本线程之前设置的值,才能删除这把锁。上面的代码需要注意,value的值不能用线程名,因为多级环境下,不同节点上的线程名是可能一样的。但是由于获取key和删除key的操作不是原子的,当线程A执行完redis.get(key) == value之后,执行redis.delete(key)之前,锁过期了,此时线程B加了锁,线程A依然可能删除线程B持有的锁,所以,判断redis.get(key) == value,redis.delete(key)这两步操作也需要时原子的,这里需要通过lua脚本来保证。
即使通过lua脚本保证了释放锁的原子性,线程A业务执行过程中,锁过期了,线程B还是能够持有这把锁,这会导致并发问题。所以有锁续命的机制,就是另起线程,判断当前线程A的锁是不是快过期了,如果快过期了,就延长锁的过期时间。
从以上的描述可以看出,分布式锁的加锁的逻辑很复杂,普通开发者自己实现很容易出错,所以可以用Redisson分布式锁,已经帮我们解决了以上的问题,且代码十分简单。
try {
//加锁
RLock redissonLock = redisson.getLock(key);
redissonLock.lock();
System.out.println("执行业务逻辑");
} finally {
// 释放锁
redisson.unlock();
}
以上分布式锁还可能出现问题,就是redis主节点挂了,从节点上并没有锁,然后从节点选举为主节点,之后其他线程仍然可能加锁成功。
redlock 需要超过半数的redis节点加锁成功才算加锁是成功的