简单梳理Redis分布式锁 知识点
应用场景:
当多个应用进程(客户端)需要互斥地访问共享资源时,可以使用分布式锁。其中Redis官方权威提出了RedLock。Java中可使用Redssion提供的实现。
注意点:
- 互斥,保证任何时刻只能有一个客户端 获取到锁;
- 效率之死锁,保证获取到锁的客户端即使在出现网络分区或者宕机的情况下,也能释放掉锁;
- 效率之容错,保证只要大多数Redis节点正常工作,客户端就能正常获取到锁和正常释放锁。
原理:
获取锁:
SET resource_name my_random_value NX [EX|PX] 30000
Redssion中的实现使用了Hash, 可重入:
if (redis.call(‘exists’, KEYS[1]) == 0)
then redis.call(‘hset’, KEYS[1], ARGV[2], 1);
redis.call(‘pexpire’, KEYS[1], ARGV[1]);
return nil;
end;
if (redis.call(‘hexists’, KEYS[1], ARGV[2]) == 1)
then redis.call(‘hincrby’, KEYS[1], ARGV[2], 1);
redis.call(‘pexpire’, KEYS[1], ARGV[1]);
return nil;
end;
return redis.call(‘pttl’, KEYS[1]);
Lua脚本解释:
如果锁对应的Hash, key=resource_name的数据不存在,
那么就设置:
hset resource_name my_random_value 1
pexpire resource_name 30000
如果锁对应的Hash下, fieldKey=my_random_value存在,
那么对其加1,即执行:
hincrby resource_name my_random_value 1
pexpire resource_name 30000
最后返回pttl resource_name
释放锁:
if redis.call(“get”,KEYS[1]) == ARGV[1] then
return redis.call(“del”,KEYS[1])
else
return 0
end
Redisson的释放锁实现:
if (redis.call(‘hexists’, KEYS[1], ARGV[3]) == 0)
then return nil;
end;
local counter = redis.call(‘hincrby’, KEYS[1], ARGV[3],
-1);
if (counter > 0)
then redis.call(‘pexpire’, KEYS[1], ARGV[2]);
return 0;
else redis.call(‘del’, KEYS[1]);
redis.call(‘publish’, KEYS[2], ARGV[1]);
return 1;
end;
return nil;
Redis分布锁常见的几种实现方案:
-
单机
缺点:单点故障,整个服务无法使用。 -
主从复制
缺点:failover问题。 根本原因在于主从复制有延迟。当master在slave复制成功前宕机,slave晋升为master时没有宕机时的锁信息,导致有可能出现两个客户端同时持有同一把锁。
举例:
a. client A 在master拿到锁
b. master节点把A创建的锁信息同步到slave之前宕机了
c. slave晋升为master节点
d. client B 在新master拿到与A相同的锁(此时A以为自己仍持有锁)
…TODO
Redisson使用DEMO代码片段:
使用Redisson加锁 释放锁业务代码