分布式锁的实现方式有哪些?

本文深入探讨了分布式锁的重要性及其实现方式,包括基于数据库、Redis和Zookeeper的三种主要方法,旨在解决分布式系统中资源访问的互斥问题。

分布式是在大型的系统中经常使用的技术,在使用的过程中为了使得进程之间不会相互干扰就需要使用分布式锁

为什么要使用分布式锁

为了保证一个方法或属性在高并发的情况下同一时间只能被同一个线程执行,在传统单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLock或Synchronized)进行互斥控制。但是,随之业务发展的需要,原单机部署的系统演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同的机器上,这将原来的单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。
为了解决这个问题,就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!

看看下图,我们知道随着业务发展、微服务化,一个应用需要部署到多台服务器上然后做负载均衡,大概的架构图如下:
在这里插入图片描述

分布式锁的三种实现方式

1. 基于数据库实现分布式锁
2. 基于缓存(例如Redis)实现分布式锁
3. 基于Zookeeper实现分布式锁

1.基于数据库实现分布式锁

在这里插入图片描述

  1. **悲观锁:**利用select … where … for update 排他锁

注意: 其他附加功能与实现一基本一致,这里需要注意的是“where name=lock ”,name字段必须要走索引,否则会锁表。有些情况下,比如表不大,mysql优化器会不走这个索引,导致锁表问题。

  1. 乐观锁:

所谓乐观锁与前边最大区别在于基于CAS思想,是不具有互斥性,不会产生锁等待而消耗资源,操作过程中认为不存在并发冲突,只有update version失败后才能觉察到。我们的抢购、秒杀就是用了这种实现以防止超卖。通过增加递增的版本号字段实现乐观锁

2.基于缓存(例如Redis)实现分布式锁

在这里插入图片描述

  1. 使用命令介绍:
    (1)SETNX
    SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
    (2)expire
    expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
    (3)delete
    delete key:删除key

在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。

  1. 实现思想:
    (1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
    (2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
    (3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
3,基于Zookeeper实现分布式锁

在这里插入图片描述
ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下:

(1)创建一个目录mylock;
(2)线程A想获取锁就在mylock目录下创建临时顺序节点;
(3)获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
(4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
(5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。

这里推荐一个Apache的开源库Curator,它是一个ZooKeeper客户端,Curator提供的InterProcessMutex是分布式锁的实现,acquire方法用于获取锁,release方法用于释放锁。

优点:具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。

缺点:因为需要频繁的创建和删除节点,性能上不如Redis方式。

### 分布式锁的特性 分布式锁需要满足特定的特性以确保在分布式系统中能够正常工作。以下是分布式锁的主要特性: - **互斥性**:分布式锁必须保证同一时间只有一个客户端能够持有锁,从而避免多个节点同时访问共享资源[^3]。 - **可重入性**:支持一个线程多次获取同一个锁,并且只有当该线程释放相同次数的锁后,其他线程才能获取该锁[^3]。 - **锁超时释放**:为了避免死锁问题,分布式锁通常会设置一个超时时间,超过该时间后锁会被自动释放[^3]。 - **高性能和高可用性**:锁的获取和释放操作需要高效,能够支持高并发场景;同时需要具备高可用机制,防止锁服务不可用导致系统故障[^3]。 - **安全性**:锁只能被持有它的客户端删除,其他客户端无法删除不属于自己的锁[^3]。 - **阻塞性**:支持阻塞和非阻塞两种方式获取锁,阻塞方式会在锁被占用时等待,而非阻塞方式则直接返回失败[^3]。 - **公平性**:支持公平锁和非公平锁两种类型,公平锁按照请求顺序分配锁,而非公平锁则不保证顺序[^3]。 ### 分布式锁实现方法 分布式锁可以通过多种方式实现,以下是一些常见的实现方法及其特点: #### 1. 基于 Redis 的实现 Redis 是一种内存数据库,其单线程特性和原子操作使其成为实现分布式锁的理想选择。基于 Redis 的分布式锁主要依赖 `SETNX`(Set if Not Exists)命令来实现锁的获取和释放[^4]。以下是具体实现步骤: - 使用 `SETNX` 命令尝试设置一个键值对,如果键不存在则设置成功,表示获取锁成功。 - 设置锁的过期时间,防止死锁发生。 - 在释放锁时,确保只有持有锁的客户端能够删除对应的键值对。 代码示例: ```python import redis import time client = redis.StrictRedis() def acquire_lock(lock_name, acquire_timeout=10): identifier = str(uuid.uuid4()) end = time.time() + acquire_timeout while time.time() < end: if client.setnx(lock_name, identifier): client.expire(lock_name, 10) # 设置锁的过期时间 return identifier time.sleep(0.001) return None def release_lock(lock_name, identifier): script = """ if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end """ return client.eval(script, 1, lock_name, identifier) ``` #### 2. 基于 ZooKeeper 的实现 ZooKeeper 是一种分布式协调服务,通过其临时顺序节点可以实现分布式锁。以下是其实现原理: - 创建一个临时顺序节点,ZooKeeper 会为每个节点分配一个全局唯一的递增序号。 - 客户端检查当前创建的节点是否是序号最小的节点,如果是,则表示获取锁成功。 - 释放锁时,删除对应的临时节点即可[^2]。 #### 3. 基于版本字段的乐观锁 乐观锁通过版本号或时间戳来控制并发访问。以下是其实现方式: - 每次更新数据时,先读取当前版本号或时间戳。 - 更新时比较当前版本号与数据库中的版本号,如果一致则更新成功,否则更新失败。 - 这种方式适用于读多写少的场景,但不适用于需要长时间持有锁的场景[^4]。 ### 总结 分布式锁实现方式各有优劣,基于 Redis 的实现简单高效,适合大多数场景;基于 ZooKeeper 的实现功能强大,但复杂度较高;基于版本字段的乐观锁适用于特定场景,但不支持长时间锁持有。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

良缘白马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值