分布式锁是控制分布式系统或不一样系统之间共同访问共享资源的一种锁实现 若是不一样的系统或同一个系统的不一样主机之间共享了某个资源时,每每经过互斥来防止彼此干扰。java
使用setnx命令加锁,key是锁的惟一标识,能够根据业务来命名,value为当前线程的ID或者UUID(后面介绍缘由) 好比扣减商品库存,key但是 lock_stock_upc ,value能够为当前线程ID。python
若是一个获得锁的线程在执行任务的过程当中挂掉,来不及显式地释放锁,这块资源将会永远被锁住,别的线程再也别想进来。那么这个锁就永远被没法获取到 因此,咱们须要给setnx的key必须设置一个超时时间,以保证在异常状况下即便锁没有被显式释放,这把锁也要在必定时间后自动释放。redis
当获得锁的线程执行完任务,须要释放锁,以便其余线程能够进入。释放锁的最简单方式是执行del指令, del(key)释放锁以后,其余线程就能够继续执行setnx命令来得到锁。算法
因为set和expire的非原子性,会致使一个异常状况 bash
因此要保证SETNX和SETEX(设置过时时间)这2个命令一块儿执行,要么都成功,要么都失败,保证其原子性。服务器
假如某线程成功获得了锁,而且设置的超时时间是30秒。若是某些缘由致使线程B执行的很慢很慢,过了30秒都没执行完,这时候锁过时自动释放,线程B获得了锁。分布式
线程A执行完了任务,线程A接着执行del指令来释放锁。但这时候线程B还没执行完,线程A实际上删除的是线程B加的锁。性能
怎么避免这种状况呢?能够在del释放锁以前作一个判断,验证当前的锁是否是本身加的锁。ui
至于具体的实现,能够在加锁的时候把当前的线程ID当作value,并在删除以前验证key对应的value是否是本身线程的ID。 这就是前面说的设置value的时候要设置成uuid或者线程ID的缘由。spa
if(threadId .equals(redisClient.get(key))){
del(key)
}
复制代码
然而因为,if判断和释放锁是两个独立操做,不是原子性,因此采用Lua脚本释放锁。后面介绍实现。
经过以上可知,要想实现一个可靠的分布式锁,设计锁的时候须要考虑一下要素:
SET key value NX PX 30000
value是由客户端生成的一个随机字符串,至关因而客户端持有锁的标志
NX表示只有key值不存在的时候才能SET成功,至关于只有第一个请求的客户端才能得到锁
PX 30000表示这个锁有一个30秒的自动过时时间。
复制代码
为了防止客户端1得到的锁,被客户端2给释放,采用下面的Lua脚原本释放锁
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
复制代码
假如在redis sentinel集群中,咱们具备多台redis,他们之间有着主从的关系,例如一主二从。咱们的set命令对应的数据写到主库,而后同步到从库。当咱们申请一个锁的时候,对应就是一条命令 setnx mykey myvalue ,在redis sentinel集群中,这条命令先是落到了主库。假设这时主库down了,而这条数据还没来得及同步到从库,sentinel将从库中的一台选举为主库了。这时,咱们的新主库中并无mykey这条数据,若此时另一个client执行 setnx mykey hisvalue , 也会成功,即也能获得锁。这就意味着,此时有两个client得到了锁。这不是咱们但愿看到的,虽然这个状况发生的记录很小,只会在主从failover的时候才会发生,大多数状况下、大多数系统均可以容忍,可是不是全部的系统都能容忍这种瑕疵。
为了解决故障转移状况下的缺陷,Antirez 发明了 Redlock 算法,使用redlock算法,须要多个redis实例,加锁的时候,它会想多半节点发送 setex mykey myvalue 命令,只要过半节点成功了,那么就算加锁成功了。释放锁的时候须要想全部节点发送del命令。这是一种基于【大多数都赞成】的一种机制。咱们能够选择已有的开源实现,python有redlock-py,java 中有Redisson redlock。
redlock确实解决了上面所说的“不靠谱的状况”。可是,它解决问题的同时,也带来了代价。你须要多个redis实例,你须要引入新的库 代码也得调整,性能上也会有损。因此,果真是不存在“完美的解决方案”,咱们更须要的是可以根据实际的状况和条件把问题解决了就好。