redis实现分布式锁的几种方式 分布式锁的实现方式redis( 二 )


12、例如,以下命令将锁的过期时间设置为10秒 。SETlock_key1EX10NX到目前为止,死锁问题已经解决,但仍然存在其他问题 。想象以下场景:
13、客户端1锁定成功,开始操作共享资源 。
14、客户端1操作共享资源时间过长,超过锁到期时间,锁失效(锁自动释放) 。客户端2锁定成功,开始操作共享资源 。客户端1操作共享资源,手动释放finally块中的锁,但此时释放客户端2的锁 。
15、这里有两个严重的问题:锁过期释放了别人的锁之一个问题是由于对操作共享资源的时间的不准确估计而引起的 。如果只是一味的增加到期时间,只能缓解问题,降低出现问题的概率,仍然不能彻底解决问题 。原因是客户端获得锁后,在操作共享资源时,遇到的场景非常复杂 。
16、既然是预估时间,只能粗略计算,不可能涵盖所有导致耗时更长的场景 。第二个问题是解除别人的锁,因为解除锁的操作是无脑的,没有检查锁的所有权,所以开锁不严谨 。怎么解决?锁被别人打开了 。
17、解决方案是,当客户端锁定锁时,它设置一个只有它自己知道的唯一标识符,例如,它可以是自己的线程ID 。如果是redis实现的,则设置keyunique_valueEX10NX 。之后,在解锁的时候,首先要判断锁是不是自己的,只有自己的才能解锁 。
18、//释放锁比较unique_value是否相等,避免误释放ifredis.get("key")==unique_valuethenreturnredis.del("key")这里,GET+DEL命令用于释放锁 。这时候又会遇到原子问题 。客户端1执行GET,判断锁是自己的客户端2执行了SET命令,强制获取到锁(虽然发生概率很低,但要严谨考虑锁的安全性)客户端1执行DEL,却释放了客户端2的锁所以上面的GET+DEL命令还是要用原子来执行 。
19、原子如何执行两个命令?答案是Lua脚本 。可以把上面的逻辑写成Lua脚本,让Redis执行 。因为Redis处理的每个请求都是在单个线程中执行的,所以在执行一个Lua脚本的时候,其他请求必须等到Lua脚本处理完,这样在GET+DEL之间就不会有其他命令执行了 。
20、下面是使用Lua脚本(unlock.script)释放锁的伪代码,其中KEYS[1]代表lock_key,ARGV[1]是当前客户端的唯一ID 。当我们执行Lua脚本时,这两个值都作为参数传入 。//Lua脚本语言,释放锁比较unique_value是否相等,避免误释放ifredis.call("get",KEYS[1])==ARGV[1]thenreturnredis.call("del",KEYS[1])elsereturn0end最后,我们执行下面的命令 。
21、redis-cli--evalunlock.scriptlock_key,unique_value这样一路下来,整个上锁开锁的过程会更加严谨 。首先,我们来总结一下基于Redis的分布式锁 。一个严谨的流程如下:加锁时要设置过期时间SETlock_keyunique_valueEXexpire_timeNX操作共享资源释放锁:Lua脚本,先GET判断锁是否归属自己,再DEL释放锁有了这个严谨的锁模型,我们需要重新思考之前的问题,如果锁的到期时间不好评估怎么办 。
22、如何确定锁的到期时间如前所述,如果到期时间没有评估好,这个锁将有提前到期的风险 。一种折中的解决方案是尽可能使到期时间冗余,降低锁提前到期的概率,但这种解决方案并不能完美解决问题 。有可能设置这样的方案吗?锁的时候,先设置一个估计的到期时间,然后启动一个守护线程定期检测这个锁的到期时间 。
23、如果锁即将到期,而共享资源的操作没有完成,那么锁将自动更新,到期时间将重置 。这是一个更好的方案 。已经有一个库封装了所有这些作品,它就是Redisson 。
24、Redisson是一个用Java语言实现的Redisdk客户端 。当使用分布式锁时,它采用自动更新方案来避免锁过期 。这个守护线程通常被称为看门狗线程 。

推荐阅读