分布式锁-基于Redis实现分布式锁

基于ZooKeeper 实现的分布式锁吞吐量有限,而基于Redis 实现的分布式锁的吞吐量至少可以提升一个数量级。

分布式锁-基于ZooKeeper的分布式锁

Redis 单节点实现方式

通过单点实现分布式锁的核心是围绕 SETNX(SET if Not eXist ),当key 不存在时返回1,当key 存在时返回0。

因为Redis 是单线程的,所以在Redis 服务侧不会有线程安全问题。当返回1的时候,认为获得锁成功,可以进行相应的业务逻辑处理,处理完成后,删除key,释放锁;当返回0时,表示获取锁失败。

分布式锁-基于Redis实现分布式锁

获取锁使用命令:

SET resource_name my_random_value NX PX 30000

这个命令仅在不存在key的时候才能被执行成功(NX选项),并且这个key有一个30秒的自动失效时间(PX属性)。这个key的值是“my_random_value”(一个随机值),这个值在所有的客户端必须是唯一的,所有同一key的获取者(竞争者)这个值都不能一样。

value的值必须是随机数主要是为了更安全的释放锁,释放锁的时候使用脚本告诉Redis:只有key存在并且存储的值和我指定的值一样才能告诉我删除成功。可以通过以下Lua脚本实现:

if redis.call(“get”,KEYS[1]) == ARGV[1] then

return redis.call(“del”,KEYS[1])

else

return 0

end

使用这种方式释放锁可以避免删除别的客户端获取成功的锁。举个例子:客户端A取得资源锁,但是紧接着被一个其他操作阻塞了,当客户端A运行完毕其他操作后要释放锁时,原来的锁早已超时并且被Redis自动释放,并且在这期间资源锁又被客户端B再次获取到。如果仅使用DEL命令将key删除,那么这种情况就会把客户端B的锁给删除掉。使用Lua脚本就不会存在这种情况,因为脚本仅会删除value等于客户端A的value的key(value相当于客户端的一个签名)。

key的失效时间,被称作“锁定有效期”。它不仅是key自动失效时间,而且还是一个客户端持有锁多长时间后可以被另外一个客户端重新获得。

Redlock实现方案

Redlock 是Redis 的作者Antirez在Redis 官网中给出的一种基于Redis的分布式锁方案。就是N(通常是5)个独立的Redis 节点同时使用SETNX,如果多数节点成功,则拿到锁,这样就允许少数节点不可用的情况。整个取锁、释放锁的操作和单节点类似。

分布式锁-基于Redis实现分布式锁

是不是这样就完美了呢?当然不是。

1. 重启问题

假设一共有5个节点(A/B/C/D/E),service1成功获取了锁,注意这里,service1在A/B/C上setnx成功,但是并没有在D/E上成功,如果C节点挂掉,又恰巧重启了,如果C节点并没有持久化,这时候service2也可以成功锁住C/D/E,导致锁失效。

有人说,那C持久化不就行了吗,实际上设置为同步的持久化方式对性能影响比较大。也就是常说的始终追加同步,如果是机械硬盘,吞吐量可能会从10万级降到几百。有人说用固态硬盘不就解决了吗?土豪是可以用的,吞吐量确实能到万级,但是大量的、频繁的写入容易导致写入放大。

这个问题的解决方案也非常简单。就是延迟重启(elayed restarts),说白了就是等到挂掉节点上的所有锁都过期了再重启,重启后,以前的锁都已经失效。

2. 响应失败

如果一个节点获取锁成功了,四个节点都setnx成功,一个失败了,失败的情况是:发起请求成功,在返回ack的时候失败,这时候客户端会认为是失败了,而删除锁的时候没有删除这个节点,这就会导致过期之前,这个节点获取锁一直失败,所以,正确的做法是无论setnx成功还是失败,都应该执行一次删除操作。

分布式锁-基于Redis实现分布式锁

来源:软件架构

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2019年3月20日
下一篇 2019年3月20日

相关推荐