分布式锁详解
1.分布式锁需要满足的要求
- 加锁与释放锁都是原子的
- 共享存储系统保存了锁变量,因此共享存储系统要保证自身的可靠性
2. redis分布式锁
2.1. set key方式实现分布式锁
2.1.1 实现原理
SET key value [EX seconds | PX milliseconds] [NX | XX]
-
EX seconds
:设置键的过期时间,单位为秒。 -
PX milliseconds
:设置键的过期时间,单位为毫秒。 -
NX
:只有当键不存在时,才对键进行设置操作。 -
XX
:只有当键已经存在时,才对键进行设置操作
// 加锁, unique_value作为客户端唯一性的标识
SET lock_key unique_value EX 20 NX
设置一个key为lock_key,value为unique_value 的锁,有效时间20秒,
且当锁之前不存在时才能加锁成功
过期时间保证了:即便解锁失败,也不会长期占用
2.1.2 存在问题
- 自动续期问题
- 重入性问题
2.1.3 该进
解决续期问题
待续......
2.1.4 spring boot集成
2.2. redission提供的分布式锁
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.16.8</version>
</dependency>
2.2.1 实现代码
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.config.Config;
public class CacheBreakthrough {
private static final String KEY = "lock_test";
private static final Redisson redisson;
static {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
redisson = Redisson.create(config);
}
public static Object lockTest() {
RLock lock = redisson.getLock(KEY);
try {
检测重入性
lock.lock();
lock.lock();
lock.lock();
}finally{
lock.unlock();
lock.unlock();
lock.unlock();
}
}
2.2.2 redis储存形式
使用hash结构存储
锁的默认30秒的有效期,自动续期时间10秒会检测一次任务是否完成,如果未完成,自动续期
2.2.3 如何实现可重入的
使用hash结构存储,key为锁名称key
hash中元素只有1个键值对,且key为当前线程标识,value为加锁次数,默认为1。
2.2.4 如何实现自动续期
-
看门狗机制:当一个线程获取到锁时,Redisson 会启动一个看门狗后台线程。该线程会定期检查当前线程是否仍然持有锁,如果是,则不断延长锁的生存时间。
-
默认续期时间:Redisson 的锁自动续期默认时间是 30 秒,这个时间是可以被修改的。
-
续期操作:看门狗线程每间隔一定时间(默认为 30 秒的一半即 15 秒)检查当前线程是否仍然持有锁,如果是则延长锁的过期时间。
2.2.4.1 注意事项
-
自动续期条件:只有当使用
lock()
方法获取锁时,Redisson 才会自动续期。如果使用lock(long leaseTime, TimeUnit unit)
方法指定锁的过期时间,则不会自动续期。 -
手动续期:在某些情况下,如果业务逻辑非常复杂,可能需要手动续期。可以使用
lock.expire
方法手动延长锁的过期时间。
2.2.4.2 看门狗实现的原理
起一个后台线程,定时查看要续期的key是否存在,如果存在,则表示还在处理业务,需要续期,然后使用expireTime修改key的过期时间
(未完.....)
3. mysql分布式锁
利用唯一索引的特性
3.1 简单的设计
3.1.1 实现原理
id,
lock_key: 唯一索引,由业务生成唯一标识作为锁
expire_time: 过期时间(启动一个后台线程扫描过期的key,然后删除,避免“释放锁失败导致的不会过期”)
当插入数据失败时,则表示加锁失败
3.1.2 存在问题
- 重入性
- 被其他事务释放
- 不会自动过期
3.2 复杂的设计
表字段:
id
lock_key: 唯一索引,由业务生成唯一标识作为锁
thread_id: 结合加锁的线程生成唯一的标识,避免被其他线程解锁
lock_num: 重入的次数(默认1,加锁时,先判断当前lock_key是否存在,如果存在则验证thread_id: 如果通过,则把lock_num+1)
expire_time: 过期时间(启动一个后台线程扫描过期的key,然后删除,避免“释放锁失败导致的不会过期”)
4. zookeeper分布式锁
未用过
5. 结论
经常使用redission提供的redis锁;
非高并发业务,也可使用mysql分布式锁的简单设计方案(一个业务,很少有需要重入锁的情况)