redission是官方推荐的,用于实现分布式锁的项目,而且是java写的,对于java开发人员来说无疑是非常友好。
redission可以支持redis cluster、master-slave、redis哨兵和redis单机。
redission使用起来非常简单,我们简单看一下案例:
RLock mylock = redisson.getLock(key); // key字符串随便取
mylock.lock(2, TimeUnit.MINUTES); // lock提供带timeout参数,timeout结束强制解锁,防止死锁
// ....
lock.unlock();
Redission是怎么实现
(1)加锁机制
假设是一个redis cluster,客户端1会根据hash算法选择一个节点,发送lua脚本,之所以发送lua脚本,是因为要保证原子性。
"if(redis.call('exist'),KEYS[1]==0) then" + // KEY[1]就是key,我们假设是mylock
"redis.call('hset',KEYS[1],ARGV[2], 1);"+ // ARGV[2]就是客户端的ID
"redis.call('pexpire', KEYS[1], ARGV[1]);"+ // ARGV[1]代表的就是锁key的默认生存时间,默认30秒
"return nil;" +
"end;" +
"if (redis.call('hexists', KEYS[1], ARGV[2] == 1)) then" +
"redis.call('hincrby',KEYS[1],ARGV[2],1);" +
"reids.call('pexipre',KEYS[1], ARGV[1]);" +
"return nil;" +
"end" +
"return redis.call('pttl', KEYS[1])"
客户端1成功加锁,客户端2来了,一看发现第一个if,发现mylock这个锁key已经存在了,就走第二个if,一看发现没有自己的客户端ID,所以客户端ID会获取到mylock这个key的剩余时间。之后客户端2会进入一个while循环,不停的尝试加锁。
(2)watch dog自动延期
redission还提供了watch dog线程,客户端1加锁的key默认是30s,但是客户端1业务还没有执行完,时间就过了,客户端1还想持有锁的话,就会启动一个watch dog后台线程,不断的延长锁key的生存时间。
(3)可重入锁
同时,如果客户端1重复加锁,也是支持,无非就是hset +1,代表的加锁的次数+1,不过代码中记得要unlock()掉。
(4)释放锁
释放锁,其实就是将加锁次数-1,如果发现加锁次数是0,说明这个客户端已经不再持有锁,就会执行del mylock
命令,从redis里把这个kv删掉,这样客户端2就可以加锁了。
(5)缺点
因为是redis cluster,这个kv会被异步复制给其他节点。但是在这过程中主节点挂了,还没来得及复制。虽然客户端1以为加锁成功了,其实这个key已经丢失。
主备切换后,客户端2也来加锁,也成功了,这样就导致了多个客户端对一个分布式锁完成了加锁,可能会造成脏数据。
这也是Redisson在master-slave、redis cluster的缺陷。