分布式锁留神点
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)
}
}
留神点
SETNX 和 EXPIRE 非原子性
如果 SETNX 胜利,在设置锁超时工夫后,服务器挂掉、重启或网络问题等,导致 EXPIRE 命令没有执行,锁没有设置超时工夫变成死锁。
解决办法
- 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
谬误解除
如果线程 A 胜利获取到了锁,并且设置了过期工夫 30 秒,但线程 A 执行工夫超过了 30 秒,锁过期主动开释,此时线程 B 获取到了锁;随后 A 执行实现,线程 A 应用 DEL 命令来开释锁,但此时线程 B 加的锁还没有执行实现,线程 A 理论开释的线程 B 加的锁。
解决办法
- 开释锁的时候,只能开释本人的,不能开释他人,所以须要 getValue 而后和本人的 id 比拟,统一了能力 delete,这里也须要是原子操作(其实感觉能够通过重试机制来防止)
超时解锁导致并发
如果线程 A 胜利获取锁并设置过期工夫 30 秒,但线程 A 执行工夫超过了 30 秒,锁过期主动开释,此时线程 B 获取到了锁,线程 A 和线程 B 并发执行。
A、B 两个线程产生并发显然是不被容许的,个别有两种形式解决该问题:
- 将过期工夫设置足够长,确保代码逻辑在锁开释之前可能执行实现。
- 为获取锁的线程减少守护线程,为将要过期但未开释的锁减少无效工夫。
不可重入
当线程在持有锁的状况下再次申请加锁,如果一个锁反对一个线程屡次加锁,那么这个锁就是可重入的。如果一个不可重入锁被再次加锁,因为该锁曾经被持有,再次加锁会失败。Redis 可通过对锁进行重入计数,加锁时加 1,解锁时减 1,当计数归 0 时开释锁。
主备切换
为了保障 Redis 的可用性,个别采纳主从形式部署。主从数据同步有异步和同步两种形式,Redis 将指令记录在本地内存 buffer 中,而后异步将 buffer 中的指令同步到从节点,从节点一边执行同步的指令流来达到和主节点统一的状态,一边向主节点反馈同步状况。
在蕴含主从模式的集群部署形式中,当主节点挂掉时,从节点会取而代之,但客户端无显著感知。当客户端 A 胜利加锁,指令还未同步,此时主节点挂掉,从节点晋升为主节点,新的主节点没有锁的数据,当客户端 B 加锁时就会胜利。
集群脑裂
集群脑裂指因为网络问题,导致 Redis master 节点跟 slave 节点和 sentinel 集群处于不同的网络分区,因为 sentinel 集群无奈感知到 master 的存在,所以将 slave 节点晋升为 master 节点,此时存在两个不同的 master 节点。Redis Cluster 集群部署形式同理。
当不同的客户端连贯不同的 master 节点时,两个客户端能够同时领有同一把锁。如下:
论断
Redis 以其高性能著称,但应用其实现分布式锁来解决并发仍存在一些艰难。Redis 分布式锁只能作为一种缓解并发的伎俩,如果要齐全解决并发问题,仍须要数据库的防并发伎俩。
参考文档
- 分布式锁的实现之 redis 篇
- Redis 分布式锁进化史解读 + 缺点剖析
- Redis RedLock 完满的分布式锁么?
- 大家所推崇的 redis 分布式锁真的就十拿九稳么?