分布式锁注意点及其实现
来源:SegmentFault思否社区
作者:skyarthur
分布式锁注意点
1)互斥性
在任意时刻只有一个客户端可以获取锁
2)防死锁
假如一个客户端在持有锁的时候崩溃了,没有释放锁,那么别的客户端无法获得锁,则会造成死锁,所以要保证客户端一定会释放锁。Redis中我们可以设置锁的过期时间来保证不会发生死锁。
3)持锁人解锁
解铃还须系铃人,加锁和解锁必须是同一个客户端,客户端A的线程加的锁必须是客户端A的线程来解锁,客户端不能解开别的客户端的锁。
4)可重入
当一个客户端获取对象锁之后,这个客户端可以再次获取这个对象上的锁。
redis分布式锁
实现
Redis 锁主要利用 Redis 的 setnx 命令。
加锁命令:SETNX key value,当键不存在时,对键进行设置操作并返回成功,否则返回失败。KEY 是锁的唯一标识,一般按业务来决定命名。 解锁命令:DEL key,通过删除键值对释放锁,以便其他线程可以通过 SETNX 命令来获取锁。 锁超时:EXPIRE key timeout, 设置 key 的超时时间,以保证即使锁没有被显式释放,锁也可以在一定时间后自动释放,避免资源被永远锁住。
if (setnx(key, 1) == 1){
expire(key, 30)
try {
//TODO 业务逻辑
} finally {
del(key)
}
}
注意点
![](https://filescdn.proginn.com/905ec369a892401a3cc7725c78a1f048/2c2502300c6c0fd1fba9769d025fb99c.webp)
setnx的时候,value设置和过期时间设置不是原子,可以用set命令,redis版本2.6.12以后 lua脚本,示例如下
if (redis.call('setnx', KEYS[1], ARGV[1]) < 1)
then return 0;
end;
redis.call('expire', KEYS[1], tonumber(ARGV[2]));
return 1;
// 使用实例
EVAL "if (redis.call('setnx',KEYS[1],ARGV[1]) < 1) then return 0; end; redis.call('expire',KEYS[1],tonumber(ARGV[2])); return 1;" 1 key value 100
错误解除
![](https://filescdn.proginn.com/d286f5106b8986e6b9228765f5fba6d7/e8eaf8b3392ddfddac1783dff47c89d2.webp)
释放锁的时候,只能释放自己的,不能释放别人,所以需要getValue然后和自己的id比较,一致了才能delete,这里也需要是原子操作(其实觉得可以通过重试机制来避免)
超时解锁导致并发
![](https://filescdn.proginn.com/b5228126dc17d1d2a059badb85ed5d26/07ede8fcf8c8bf741f01efedf8d2f179.webp)
将过期时间设置足够长,确保代码逻辑在锁释放之前能够执行完成。 为获取锁的线程增加守护线程,为将要过期但未释放的锁增加有效时间。
![](https://filescdn.proginn.com/6196b840d254f0516531f56a4800bf33/b95d7801b2c9fb905c23aa5eddcbb104.webp)
![](https://filescdn.proginn.com/642c8327145bc62489a7e84e89e3fdf1/52f0a9d5b83275d7d07ea479da0b0299.webp)
![](https://filescdn.proginn.com/0b61a4bfde591f38d86db06f3b972f1f/b95b838ff52964b6946960307123f532.webp)
评论