redis分布式锁
setnx命令
setnx key value
将key的值设为value,仅当key不存在时。
若给定的key已经存在,则setnx不做任何动作。
SETNX 是 set if not exists 的简写
由于redis的IO单线程特性,所以同一时间只会有一个线程设值成功。
一. 利用stringRedisTemplate 构建一个原始版分布式锁(1.0版本)
String lockKey = "lock:product_101"; boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"value"); if(result){ //业务逻辑 } //释放锁 stringRedisTemplate.delete(lockKey)
二. 1.0版本存在问题
假设业务逻辑里面抛异常,则锁并未得到释放。(2.0版本
)
String lockKey = "lock:product_101"; boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"value"); if(result){ try{ //业务逻辑 }catch(Exception e){ }finally{ //释放锁 stringRedisTemplate.delete(lockKey) } }
三. 2.0版本存在问题
如果业务逻辑执行到一半,宕机了。则锁也不会得到释放
解决办法,在设置锁的时候设置10秒钟的超时时间,到期自动释放 (3.0版本
)
String lockKey = "lock:product_101"; //在设置锁的时候设置10秒钟的超时时间,到期自动释放 boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"value",10,TimeUnit.SECONDS); if(result){ try{ //业务逻辑 }catch(Exception e){ }finally{ //释放锁 stringRedisTemplate.delete(lockKey) } }
四. 关于3.0版本
大多数公司在并发量不是很高的情况下,也在允许小笔的超卖等业务的情况下,该版本适用, 该版本易于维护。
3.0版本的问题
如果在高并发的情况下,短时间大量业务请求进来的时候,程序会变慢,则设置的10秒钟自动释放可能不够用。会存在10秒钟该业务逻辑没有处理完的情况,在该情况下,锁被自动释放了。 这里当其他线程又拿到锁之后,进行业务逻辑处理。当业务逻辑处理到一部分的时候,之前的线程业务逻辑处理完了。并在这个时候把锁释放了。这里相当于A线程把B线程的锁释放了。这样C线程又会加锁成功。这样可能导致了这把锁一直失效,会导致大量的超卖。
解决办法: 将value设置成有标识的id,在释放锁的时候校验该id,属于自己的才释放该锁。(4.0版本)
这里不能设置成线程id,因为每台服务器都可能有相同的线程id
String lockKey = "lock:product_101"; //在设置锁的时候设置10秒钟的超时时间,到期自动释放 String clientId = UUID.randomUUID().toString(); boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,clientId,10,TimeUnit.SECONDS); if(result){ try{ //业务逻辑 }catch(Exception e){ }finally{ //释放锁 if(clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))){ stringRedisTemplate.delete(lockKey) } } }
五. 4.0版本存在问题
//释放锁 if(clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))){ stringRedisTemplate.delete(lockKey) }
这里释放锁逻辑会出现并发问题。当当前线程发现这把锁属于自己,并准备删除锁的时候。刚好这个时候锁超时了,锁自动释放,其他线程这个时候拿到了锁。
当前线程觉得这把锁是属于自己的,所以理所应当的把锁释放了。这时也相当于A线程把B线程的锁释放了。这个问题出现的核心是由于锁过期时间,到期自动释放导致。
解决办法: 单纯延长过期时间治标不治本。
锁续命
在主线程创建分布式锁的时候,创建一个子线程,定时(一定要小于锁过期时间)去延长锁的过期时间,让锁在主线程不退出的情况下,永远不过期。当主线程退出后,子线程也相应退出。