一、什么是分布式锁?
分布式锁,是一种跨进程、跨机器节点的互斥锁,它可以用来保证多机器节点对于共享资源访问的互斥性。
二、分布式锁的特点
想要实现一个分布式锁,主要要有以下几个特点:
- 互斥性:同一时间只能有一个线程获取到锁,然后去访问共享资源
- 可重入性:允许一个已经获取到锁的线程,在没有释放锁之前再次重新获得锁
- 避免死锁:由于分布式网络下容易出现网络延迟、节点故障等原因,死锁概率会比较高,所以需要设置一定的超时机制,让分布式锁在一定时间后自动过期删除
- …
三、如何实现一个分布式锁?
想要实现一个分布式锁,就需要依赖一个第三方的分布式组件,比如数据库、Redis等,同时考虑锁的性能问题,所以我们一般使用Redis来实现分布式锁。
1、Redis实现分布式锁
在Redis中,可以通过setnx
命令实现锁的互斥性,当key不存在就返回1,存在就返回0。另外还可以通过expire
命令设置锁的失效时间,从而避免死锁问题。
四、Q&A
1、如果锁过期了,而业务逻辑没处理完怎么办?
可以定时给锁进行续期(比如锁五分钟过期,那么可以每分钟为他续期到五分钟过期),常见的方式有通过Redisson组件来实现一个分布式锁,并且也内置了一个Watch Dog机制来对锁做续期
2、Redis的分布式锁只有setnx这一方式吗?
为了保证分布式的可靠性,尽可能减少重复加锁的问题,Redis的分布式锁的演变阶段会由setnx->Redission->RedLock
3、如何防止分布式锁被其他线程误删?
一般我们在加锁时,会将value设置为一个唯一标识(如UUID),并存在线程的本地变量中,等释放锁的时候再进行判断,只有本地变量和value相等,才认为是持有锁的线程,也允许释放锁。
大概代码为:
public class LockDemo {
public void execute(long uid) {
String uuid = getUuid();
if (!lock(uid, value)) {
return;
}
try {
// do sth
} finally {
if(uuid.equals(redis.getValue(key))) {
redis.del(key);
}
}
}
}
这里只是简单演示如何正确地释放锁,当前实际使用时还是要考虑原子性的问题,具体可以采用lua脚本的方式实现
// 释放锁时,先比较锁对应的 value 值是否相等,避免锁的误释放
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
4、如果Redis集群添加分布式锁后主节点宕机导致从节点没同步到锁数据怎么办?
背景:在Redis集群中,数据同步一般是先写入主节点,再异步通过主从同步的方式更新到从节点。如果我们添加锁成功了,但是还没来得及做主从同步就发生了主节点宕机,这时候我们通过其他节点去判断锁的话,是没有刚才的添加锁记录的,所以会出现多次添加锁成功的问题(与互斥冲突)。
解决方案:通过RedLock实现,实现原理是通过判断半数以上的节点加锁成功,那么才认为分布式锁加成功了(RedLock是直接操作Redis节点而不是集群,可以避免Redis集群主从切换导致的锁丢失问题)
缺点:性能比较差,不建议使用