编程不止是一份工作,还是一种乐趣!!!
首先,Redis客户端为了获取锁,向Redis节点发送如下命令:
SET lock_name random_val NX EX 30
public boolean lock(String lockName, String randomVal, int seconds) {
Jedis jedis = null;
try {
jedis = pool.getResource();
String rlt = jedis.set(lockName, randomVal, "NX", "EX", seconds);
return "OK".equalsIgnoreCase(rlt);
} finally {
jedis.close();
}
}
上面的命令如果执行成功,则客户端成功获取到了锁,接下来就可以访问共享资源了;而如果上面的命令执行失败,则说明获取锁失败。
注意,在上面的SET命令中:
最后,当客户端完成了对共享资源的操作之后,执行下面的Redis Lua脚本来释放锁:
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
public boolean release(String lockName, String randomVal) {
String program = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then "
+ "return redis.call(\"del\",KEYS[1]) "
+ "else return 0 "
+ "end";
Jedis jedis = null;
try {
jedis = pool.getResource();
Object rlt = jedis.eval(program, 1, lockName, randomVal);
return Long.valueOf(1) == rlt;
} finally {
jedis.close();
}
}
这段Lua脚本在执行的时候要把前面的random_val作为ARGV[1]的值传进去,把lock_name作为KEYS[1]的值传进去。至此,基于单Redis节点的分布式锁的算法就描述完了。
这里面有好几个问题需要重点分析一下。
首先第一个问题,这个锁必须要设置一个过期时间。否则的话,当一个客户端获取锁成功之后,假如它崩溃了,或者由于发生了网络分割(network partition)导致它再也无法和Redis节点通信了,那么它就会一直持有这个锁,而其它客户端永远无法获得锁了。antirez在后面的分析中也特别强调了这一点,而且把这个过期时间称为锁的有效时间(lock validity time)。获得锁的客户端必须在这个时间之内完成对共享资源的访问。
第二个问题,第一步获取锁的操作,网上不少文章把它实现成了两个Redis命令:
SETNX lock_name random_val
EXPIRE lock_name 30
虽然这两个命令和前面算法描述中的一个SET命令执行效果相同,但却不是原子的。如果客户端在执行完SETNX后崩溃了,那么就没有机会执行EXPIRE了,导致它一直持有这个锁。
第三个问题,设置一个随机字符串random_val是很有必要的,它保证了一个客户端释放的锁必须是自己持有的那个锁。假如获取锁时SET的不是一个随机字符串,而是一个固定值,那么可能会发生下面的执行序列:
之后,客户端2在访问共享资源的时候,就没有锁为它提供保护了。
第四个问题,释放锁的操作必须使用Lua脚本来实现。释放锁其实包含三步操作:’GET’、判断和’DEL’,用Lua脚本来实现能保证这三步的原子性。否则,如果把这三步操作放到客户端逻辑中去执行的话,就有可能发生与前面第三个问题类似的执行序列:
实际上,在上述第三个问题和第四个问题的分析中,如果不是客户端阻塞住了,而是出现了大的网络延迟,也有可能导致类似的执行序列发生。
前面的四个问题,只要实现分布式锁的时候加以注意,就都能够被正确处理。但除此之外还有一个问题是由failover引起的,却是基于单Redis节点的分布式锁无法解决的。这个问题是这样的。假如Redis节点宕机了,那么所有客户端就都无法获得锁了,服务变得不可用。为了提高可用性,我们可以给这个Redis节点挂一个Slave,当Master节点不可用的时候,系统自动切到Slave上(failover)。但由于Redis的主从复制(replication)是异步的,这可能导致在failover过程中丧失锁的安全性。考虑下面的执行序列:
于是,客户端1和客户端2同时持有了同一个资源的锁。锁的安全性被打破。针对这个问题,可以参考一下Redlock。