分布式锁的几种实现

针对共享资源的互斥访问历来是很多业务系统需要解决的问题。用到分布式锁说明遇到了多个进程共同访问同一个资源的问题。

一般是在两个场景下会防止对同一个资源的重复访问:

提高效率。比如多个节点计算同一批任务,如果某个任务已经有节点在计算了,那其他节点就不用重复计算了,以免浪费计算资源。不过重复计算也没事,不会造成其他更大的损失。也就是允许偶尔的失败。

保证正确性。这种情况对锁的要求就很高了,如果重复计算,会对正确性造成影响。这种不允许失败。

引入分布式锁势必要引入一个第三方的基础设施,比如 MySQL,Redis,Zookeeper 等。本文聊聊各种实现的方案。

1. 单机锁和分布式锁

由此抽象一下分布式锁的概念,首先分布式锁需要是一个资源,这个资源能够提供并发控制,并输出一个排他性的状态,也就是:

锁 = 资源 + 并发控制 + 所有权展示

以常见的单机锁为例:

Spinlock = BOOL +CAS(乐观锁)

Mutex = BOOL + CAS + 通知(悲观锁)

Spinlock 和 Mutex 都是一个 Bool 资源,通过原子的 CAS 指令:当现在为 0 设置为 1,成功的话持有锁,失败的话不持有锁,如果不提供所有权的展示,例如 AtomicInteger,也是通过资源(Interger)+ CAS,但是不会明确的提示所有权,因此不会被视为一种锁,当然,可以将“所有权展示”这个更多地视为某种服务提供形式的包装。

单机环境下,内核具备“上帝视角”,能够知道进程的存活,当进程挂掉的时候可以将该进程持有的锁资源释放,但发展到分布式环境,这就变成了一个挑战,为了应对各种机器故障、宕机等,就需要给锁提供了一个新的特性:可用性。

如下图所示,任何提供三个特性的服务都可以提供分布式锁的能力,资源可以是文件、KV 等,通过创建文件、KV 等原子操作,通过创建成功的结果来表明所有权的归属,同时通过 TTL 或者会话来保证锁的可用性,通过超时释放,可以避免死锁。

分布式锁的特性和实现.jpg

2. 分布式锁的系统分类

根据锁资源本身的安全性,我们将分布式锁分为两个阵营:

基于异步复制的分布式系统,例如 mysql,tair,redis 等。

基于 paxos 协议的分布式一致性系统,例如 zookeeper,etcd,consul 等。

基于异步复制的分布式系统,存在数据丢失(丢锁)的风险,不够安全,往往通过 TTL 的机制承担细粒度的锁服务,该系统接入简单,适用于对时间很敏感,期望设置一个较短的有效期,执行短期任务,丢锁对业务影响相对可控的服务。

基于 paxos 协议的分布式系统,通过一致性协议保证数据的多副本,数据安全性高,往往通过租约(会话)的机制承担粗粒度的锁服务,该系统需要一定的门槛,适用于对安全性很敏感,希望长期持有锁,不期望发生丢锁现象的服务。

2.1 Redis 实现

Redis 客户端加锁也要根据Redis 部署情况来使用不同的加锁方式。

2.1.1 单机Redis的分布式锁

单机方式可根据lua脚本实现

思路大概是这样的:在redis中设置一个值表示加了锁,然后释放锁的时候就把这个key删除。具体代码是这样的:

// 获取锁 // NX是指如果key不存在就成功,key存在返回false,PX可以指定过期时间 SET d_lock unique_value NX PX 30000 // 释放锁:通过执行一段lua脚本 // 释放锁涉及到两条指令,这两条指令不是原子性的 // 需要用到redis的lua脚本支持特性,redis执行lua脚本是原子性的 if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end

这种方式有两大要点:

1)一定要用SET key value NX PXmilliseconds 命令

如果不用,先设置了值,再设置过期时间,这个不是原子性操作,有可能在设置过期时间之前宕机,会造成死锁(key永久存在)。

2)value要具有唯一性

这个是为了在解锁的时候,需要验证value是和加锁的一致才删除key。

这是避免了一种情况:假设A获取了锁,过期时间30s,此时35s之后,锁已经自动释放了,A去释放锁,但是此时可能B获取了锁。A客户端就不能删除B的锁了。

如果采用单机部署模式,会存在单点问题,只要redis故障了。加锁就不行了。

2.1.2 集群分布式

redis 集群分布式集群方式有两种

master-slave + sentinel选举模式

redis cluster模式

集群使用redis锁的问题:
采用master-slave模式,如果设置锁之后,主机在传输到从机的时候挂掉了,从机还没有加锁信息,如何处理?即采用master-slave模式,加锁的时候只对一个节点加锁,即便通过sentinel做了高可用,但是如果master节点故障了,发生主从切换,此时就会有可能出现锁丢失的问题。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zgdxyg.html