使用Redis实现分布式锁主要需要解决以下几点问题:
- 安全属性(Safety property): 独享(相互排斥)。在任意一个时刻,只有一个客户端持有锁。
- 活性A(Liveness property A): 无死锁。即便持有锁的客户端崩溃(crashed)或者网络被分裂(gets partitioned),锁仍然可以被获取。
- 活性B(Liveness property B): 容错。 只要大部分Redis节点都活着,客户端就可以获取和释放锁.
(以上,来自Redis 官方文档)
实现:
目前我们所使用的Redis 在2.6.12版本之后,已经推出了非常官方的解决方案。
使用set命令就可以解决该问题。(我看现在网上还有很多帖子还在教大家使用什么setnx命令,用Lua脚本实现事务,很复杂。可能是帖子时间太久了,做法已经非常过时了。官方已经明确表达了,在之后的版本会逐渐弱化setnx,并且抛弃。)
下面我们来看一下Set命令的最新文档:
SET key value [EX seconds] [PX milliseconds] [NX|XX]
选项:
EX
seconds – Set the specified expire time, in seconds. (设置键key的过期时间,单位时秒)PX
milliseconds – Set the specified expire time, in milliseconds. (设置键key的过期时间,单位时毫秒)NX
– Only set the key if it does not already exist. (只有键key不存在的时候才会设置key的值)-
XX
– Only set the key if it already exist. (只有键key存在的时候才会设置key的值)
返回值:
simple-string-reply:如果SET
命令正常执行那么回返回OK
,否则如果加了NX
或者 XX
选项,但是没有设置条件。那么会返回nil。
Redis中文文档,Set命令的地址:http://www.redis.cn/commands/set.html
可以看出,Redis在2.6.12之后对Set方法进行了增强。已经完全支持,在插入时判断是否存在的同时,进行有效时间的设置。这样就完全解决了我们实现锁的独享(相互排斥),的同时保证了因为客户端崩溃(一直不会释放锁)引起的死锁。
下面我们举个例子看一下怎么使用这个命令:
127.0.0.1:0>set key value EX 1000 NX
OK
127.0.0.1:0>exists key
1
127.0.0.1:0>ttl key
981
127.0.0.1:0>set key value EX 1000 NX
NULL
127.0.0.1:0>ttl key
955
应用:
上面介绍的增强版set命令已经可以帮我们解决分布式锁的大部分问题了,但是如果我们自己去使用还是会面临几个问题。
- 释放锁(自己的锁必须只有自己来释放。)
- 失败时重试(其他客户端对锁的等待时间控制)
- 活性争议(防止锁过期时,多个客户端争抢锁引发脑裂)
-
性能,崩溃恢复和Redis同步(当redis 服务端发生崩溃时引发的问题)
针对以上的问题Redis官方也给我们提供了非常简单可靠的解决方案RedLock
Redis的RedLock官方文档:http://www.redis.cn/topics/distlock.html
Redis针对我们Java程序员也是非常友好的同时为我们提供了相应的解决方案Redisson
Redis的Redisson官方文档:https://github.com/redisson/redisson
Redis针对Spring框架的使用者也提供了相应的使用文档
Redis的Redisson在Spring框架中的使用文档:https://github.com/redisson/redisson/wiki/14.-Integration-with-frameworks#141-spring-framework
Redis针对我们使用Spring Boot框架的使用者也提供了对应的Spring Boot Starter
Redis的Redisson针对Spring Boot Starter的使用文档:https://github.com/redisson/redisson/tree/master/redisson-spring-boot-starter#spring-boot-starter
(Redis 在官方文档上已经解释的非常清楚了,如果各位客官还有不明白的地方,我会考虑再写一篇关于Redisson的实现原理的博客)