如何实现一个分布式锁?


一、什么是分布式锁?

分布式锁,是一种跨进程、跨机器节点的互斥锁,它可以用来保证多机器节点对于共享资源访问的互斥性。

二、分布式锁的特点

想要实现一个分布式锁,主要要有以下几个特点:

  • 互斥性:同一时间只能有一个线程获取到锁,然后去访问共享资源
  • 可重入性:允许一个已经获取到锁的线程,在没有释放锁之前再次重新获得锁
  • 避免死锁:由于分布式网络下容易出现网络延迟、节点故障等原因,死锁概率会比较高,所以需要设置一定的超时机制,让分布式锁在一定时间后自动过期删除

三、如何实现一个分布式锁?

想要实现一个分布式锁,就需要依赖一个第三方的分布式组件,比如数据库、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集群主从切换导致的锁丢失问题
缺点:性能比较差,不建议使用


文章作者: GaryLee
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 GaryLee !
  目录