Redisson 分布式锁
v1.0.0版本问题
v1.0.0版本的实现在持有锁的JVM或者持有锁的线程挂掉没有释放锁时,该锁不会被释放并且会一直占用,这个时候就使用DEL命令手动删除。
问题解决
v1.3.1版本通过key的ttl解决了这个问题,关键加锁逻辑改为了Lua脚本
加锁
,关键逻辑:
- 根据key名称获取key的value数据
- 如果value信息返回false,则代表key不存在,调用redis.call设置key和value并且设置key的超时时间为用户设置的时间或默认超时时间,默认超时时间为30秒。
- 如果value存在则json解码获取o字段(o字段表示锁的owner),如果持有锁的JVM或线程是当前线程则将c(c字段为JVM或线程持有锁/获等待锁的计数)加1
- 最后返回当前Key的TTL
local v = redis.call('get', KEYS[1]);
if (v == false) then
redis.call('set', KEYS[1], cjson.encode({['o'] = ARGV[1], ['c'] = 1}), 'px', ARGV[2]);
return nil;
else
local o = cjson.decode(v);
if (o['o'] == ARGV[1]) then
o['c'] = o['c'] + 1; redis.call('set', KEYS[1], cjson.encode(o), 'px', ARGV[2]);
return nil; " +
end;
return redis.call('pttl', KEYS[1]);
end
解锁
- 先检查锁Key是否存在,不存在返回OK,同时发布消息(问题:它想解锁而此时锁却不存在了,这是不是有可能已经出事了)
- 锁KEY还存在就检查一下owner是不是自己,如果是自己就将计数减1,如果计数还不为0就为锁续期;如果计数为0就删除锁KEY同时发布事件。
local v = redis.call('get', KEYS[1]);
if (v == false) then
redis.call('publish', ARGV[4], ARGV[2]);
return 'OK';
else
local o = cjson.decode(v);
if (o['o'] == ARGV[1]) then
o['c'] = o['c'] - 1;
if (o['c'] > 0) then
redis.call('set', KEYS[1], cjson.encode(o), 'px', ARGV[3]);
return 'FALSE';
else
redis.call('del', KEYS[1]);
redis.call('publish', ARGV[4], ARGV[2]);
return 'OK';
end
end;
return nil;
end
Lua脚本返回’OK’,'FALSE’和nil的区别:
- OK: 表示成功解锁已经被成功解锁,因为锁不存在或已经删除?
- FALSE: 此次解锁成功, 但是锁并没有被删除,因为unlock的次数还不够?
- nil: 当前线程并没有持有该锁,你为什要解锁?给你个异常让你尝尝!
问题
- 高负载下锁无法续期该怎么办?
- 不管它,由它去
- 系统容量预估,不要让你的系统中的组件有那么大的压力
- 突发情况下,万一某些业务或请求真的出问题了及时告警,业务部门该买单的就买单吧