分布式锁的一些特点
分布式锁的特点:
- 原子性: 加锁的过程, 要么执行成功, 要么执行失败, 不存在中间状态;
- 互斥性: 和本地锁一样, 互斥性是最基本的, 而分布式锁需要保证在不同节点在不同线程的互斥;
- 可重入性: 同一个节点上的同一个线程, 如果获取到这个锁之后也可以再次获取到这个锁;
- 锁超时: 和本地锁一样, 支持锁超时, 防止死锁;
- 高可用(或高可靠redLock): 加锁和解锁需要高效,同时也需要保证高可用防止分布式锁失效,可以增加降级。
- 支持阻塞和非阻塞:和ReentrantLock一样支持lock和trylock以及tryLock(long timeOut)。
- 支持公平锁和非公平锁(可选): 公平锁的意思是按照请求加锁的顺序获得锁,非公平锁就相反是无序的。这个一般来说实现的比较少。
Redis实现分布式锁
在系统中修改已有数据时,需要先读取,然后进行修改保存,此时很容易遇到并发问题。由于修改和保存不是原子操作,在并发场景下,部分对数据的操作可能会丢失。在单服务器系统我们常用本地锁来避免并发带来的问题,然而,当服务采用集群方式部署时,本地锁无法在多个服务器之间生效,这时候保证数据的一致性就需要分布式锁来实现。
二、实现
Redis 锁主要利用 Redis 的 setnx 命令。
- 加锁命令:SETNX key value(set if not exist),当键不存在时,对键进行设置操作并返回成功,否则返回失败。KEY 是锁的唯一标识,一般按业务来决定命名。
- 解锁命令:DEL key,通过删除键值对释放锁,以便其他线程可以通过 SETNX 命令来获取锁。
- 锁超时:EXPIRE key timeout, 设置 key 的超时时间,以保证即使锁没有被显式释放,锁也可以在一定时间后自动释放,避免资源被永远锁住。
则加锁解锁伪代码如下:
if (setnx(key, 1) == 1){
expire(key, 30)
try {
//TODO 业务逻辑
} finally {
del(key)
}
}
上述锁实现方式存在一些问题:
1. SETNX 和 EXPIRE 非原子性
如果 SETNX 成功,在设置锁超时时间后,服务器挂掉、重启或网络问题等,导致 EXPIRE 命令没有执行,锁没有设置超时时间变成死锁。
<