redis分布式锁的使用
避免使用 setNx 命令
1、setNx 带来的问题
在使用 setNx 命令来给程序加分布式锁的时候,一般都需要再使用 expire 命令来设置一个过期时间,避免死锁现象的发生;
但是由于这两个指令不是原子的因此,还是会出现死锁问题,例如在 setNx 执行成功后,出现宕机等问题,此时 expire 并没有执行,于是锁就无法被释放,便产生了死锁问题;
2、如何解决 setNx 的问题
要解决上述问题的产生,其实非常简单,不使用 setNx 进行加锁就行;
redis 在 2.8 版本之后,为 set 指令新增了扩展参数,可以通过 set 指令完成 setNx 的操作,由于扩展指令可以设置过期时间,所以可以将上面两个非原子指令的问题彻底解决,如下图所示:
image.png
因此在 redis 2.8 版本之后,应该避免使用 setNx 命令,而应该使用 set 的扩展参数来设置分布式锁,因为它将 setNx 和 expire 组合成一条指令,能保证指令的原子执行,能避免在使用setNx 时的死锁问题;
如何正确使用 redis 分布式锁
1、常见使用方式
try {
if (set(KEY , “true” , 2s , NX)) {
doSomething();
}
} finally {
del(KEY);
}
如上示例,是我们经常使用 redis 分布式锁的方式,这种方式虽然使用了 set 命令,避免了死锁的问题,但是这种方式有两个问题;
第一:如果线程未获取到锁,虽然它不会执行 doSomething 的逻辑,但是它会执行 finally 方法,从而会释放锁;
第二:如果线程 A 的 doSomething 方法比较耗时,执行时间超过了 2 秒钟 KEY 就过期了,于是锁被释放了,线程 B 正常获取到了锁。这时线程 A 的 doSomething 方法执行完成,于是执行了 finally 的方法释放了锁,但是由于这时锁是被线程 B 占用,于是线程 A 将线程B 的锁给释放了。
上面的问题产生的原因都是因为当前线程释放了不是由自己持有的锁而造成的,要解决这个问题就需要修改使用分布式锁的方式;
b、修改后的方式
try {
String value = UUID.randomUUID().toString();
if (set(KEY, value, 2s, NX)) {
doSomething();
}
} finally {
if (value.equals(get(KEY)) {
del( KEY );
}
}
如上所示,修改后的使用方式,主要是将之前未使用的 value 值给用起来了;
使用 set 指令时把其 value 设置为一个 UUID,释放锁时先比较 UUID 是否一致,一致才进行删除;
这样就能保证在释放锁的时候,释放锁的线程只能释放属于自己的锁,能很好的解决上面的问题;
但是这样唯一的不足就是 get 和 del 不是原子的,可能会出现锁没被及时释放的问题,但是由于有过期时间这个问题基本可以忽略;