整理 分布式锁

锁:Java中的锁可以简单的理解为多线程情况下访问临界资源的一种线程同步机制

公平锁、非公平锁、自旋锁、可重入锁、偏向锁、轻量级锁、重量级锁、读写锁、互斥锁

 

一、为什么要使用分布式锁?

(单机)在开发过程中,如果需要对某一个共享变量进行多线程同步访问的时候,使用多线程的相关技术可以完美解决。

单机模式----所有的请求都会分配到当前服务器的JVM内部,然后映射为操作系统的线程进行处理!而这个共享变量只是在这个JVM内部的一块内存空间!

集群模式---随着业务发展,需要用到集群模式,一个应用部署到多台服务器,然后做负载均衡,如下图:

 

变量A在JVM1、JVM2、JVM3三个内存中,如果不加任何控制的话 ,变量A在JVM中会同时分配到一块内存,三个请求同时发起对这个变量进行操作,显然结果是不对的,即便是非同步请求,变量A之间不存在共享,也不存在可见性,处理结果也是不对的。

如果我们业务中确实存在这个场景的话,我们就需要一种方法解决这个问题!

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

二、分布式锁具备哪些条件:

1、在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行; 
2、高可用的获取锁与释放锁; 
3、高性能的获取锁与释放锁; 
4、具备可重入特性; 
5、具备锁失效机制,防止死锁; 
6、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。

三、分布式锁的三种实现方式:

CAP告诉我们任何一个分布式系统无法同时满足一致性、高可用性、分区容错性。最多同时满足两项,在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。

基于数据库实现分布式锁; 
基于缓存(Redis等)实现分布式锁; 
基于Zookeeper实现分布式锁;

基于数据库实现分布式锁:

在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就需要使用方法名向这个表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。

使用基于数据库的这种实现方式很简单,但是对于分布式锁应该具备的条件来说,它有一些问题需要解决及优化:

1、因为是基于数据库实现的,数据库的可用性和性能将直接影响分布式锁的可用性及性能,所以,数据库需要双机部署、数据同步、主备切换;

2、不具备可重入的特性,因为同一个线程在释放锁之前,行数据一直存在,无法再次成功插入数据,所以,需要在表中新增一列,用于记录当前获取到锁的机器和线程信息,在再次获取锁的时候,先查询表中机器和线程信息是否和当前机器和线程相同,若相同则直接获取锁;

3、没有锁失效机制,因为有可能出现成功插入数据后,服务器宕机了,对应的数据没有被删除,当服务恢复后一直获取不到锁,所以,需要在表中新增一列,用于记录失效时间,并且需要有定时任务清除这些失效的数据;

4、不具备阻塞锁特性,获取不到锁直接返回失败,所以需要优化获取逻辑,循环多次去获取。

5、在实施的过程中会遇到各种不同的问题,为了解决这些问题,实现方式将会越来越复杂;依赖数据库需要一定的资源开销,性能问题需要考虑。

基于Redis的实现方式:

一、选用Redis实现分布式锁的原因?

(1)Redis有很高的性能; 
(2)Redis命令对此支持较好,实现起来比较方便

二、实现思想

(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。

(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。

(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。

 

基于ZooKeeper的实现方式:

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

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

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

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

 

 

<think>嗯,用户想了解在SpringBoot中如何用Redis或Zookeeper实现分布式锁。首先,我需要回顾一下分布式锁的基本概念,确保自己理解正确。分布式锁用于解决多实例环境下的资源竞争问题,保证同一时间只有一个实例可以访问共享资源。 接下来,用户提到的两种实现方:Redis和Zookeeper。我需要整理这两种方法的步骤和关键点。对于Redis,常用的方法是使用SET命令配合NX和EX参数,确保原子性操作。还要考虑的释放机制,比如使用Lua脚本来保证原子性。另外,可能还需要提到Redisson库,因为它提供了更高级的分布式锁实现,比如看门狗机制自动续期,避免过期问题。 然后是Zookeeper的实现方,基于临时顺序节点。客户端创建临时节点,判断是否是最小节点来获取。如果失败,就监听前一个节点。这里需要解释Zookeeper的临时节点特性,比如会话结束自动删除,以及顺序节点避免惊群效应。Curator框架可以简化这些操作,SpringBoot整合的话需要添加依赖和配置连接信息。 还需要比较两者的优缺点。Redis性能高,但可靠性依赖于持久化机制,可能存在过期但业务未执行完的情况。Zookeeper基于CP模型,可靠性更高,但性能稍差。根据引用内容,用户提供的资料里提到Zookeeper的可靠性高于Redis,这点需要强调。 然后考虑用户可能的疑问,比如如何选择这两种方,或者具体的实现细节。比如代码示例,可能用户需要看到SpringBoot中如何配置和使用。例如,RedisTemplate的使用,或者Curator的实现。同时要注意引用中的示例代码,比如用户提供的TestController中使用RedisDistributedLock的isLock和releaseLock方法,可能需要说明这些方法的内部实现。 另外,用户的问题可能涉及分布式锁的实现原理,比如Redis的原子操作,Zookeeper的监听机制。需要确保回答覆盖这些关键点,并且结构清晰,步骤说明。同时,生成的相关问题要针对扩展知识点,比如性能析、优化方法、应用场景等,帮助用户进一步学习。 最后,确保回答符合系统级指令,正确使用LaTeX格,如行内公用$...$,独立公用$$...$$。检查引用标注是否正确,每个引用在回答末尾自然添加。例如,引用[^1]提到Zookeeper的可靠性高于Redis,可以在比较部引用。 可能还需要注意用户提到的“SpringBoot结合ZooKeeper实现分布式锁”的具体步骤,比如依赖引入、配置类编写、使用Curator的InterProcessMutex等。同时,Redis的实现中要注意版本问题,比如SET命令的原子性是否支持NX和EX参数一起使用,或者是否需要Lua脚本保证原子性,根据引用中的代码示例,用户可能已经实现了这种方。 总结回答的结构:先介绍分布式锁的作用,然后Redis和Zookeeper两种方详细说明实现步骤,包括原理、代码示例、依赖配置,接着比较两者的优缺点,最后生成相关问题。确保每个部都有对应的引用支持,比如引用[^3]提到Redission和Zookeeper的实现,引用[^5]解释Zookeeper节点竞争的过程。</think>在SpringBoot中实现分布式锁主要有Redis和Zookeeper两种主流方案,二者在实现原理和特性上有显著差异,具体实现步骤如下: ### 一、基于Redis的分布式锁 #### 1. 实现原理 通过`SET key value NX EX`命令实现原子性加,其中: - `NX`表示键不存在时才能设置成功 - `EX`设置键的过期时间,防止死 $$ \text{获取公}:\ SET\ lockKey\ randomValue\ NX\ EX\ ttl $$ #### 2. 代码实现 ```java public class RedisDistributedLock { public static boolean isLock(String key, String value, RedisTemplate<Object,Object> redisTemplate) { return redisTemplate.opsForValue().setIfAbsent(key, value, Duration.ofSeconds(30)); } public static void releaseLock(String key, String value, RedisTemplate<Object,Object> redisTemplate) { // 使用Lua脚本保证原子性 String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Collections.singletonList(key), value); } } ``` 在Controller中调用: ```java @PostMapping("/order") public String createOrder() { Boolean isLock = RedisDistributedLock.isLock("testLock", "456789", redisTemplate); if (!isLock) return "fail"; // 业务逻辑 RedisDistributedLock.releaseLock("testLock", "456789", redisTemplate); return "success"; } ``` > 关键点:必须验证持有者身份再释放,避免误删其他实例的[^4] #### 3. 优化建议 - 使用Redisson库实现自动续期(看门狗机制) - 采用红(RedLock)算法提升可靠性 ### 二、基于Zookeeper的分布式锁 #### 1. 实现原理 利用Zookeeper的**临时顺序节点**特性: 1. 创建临时顺序节点`/lockPath/seq-0000000001` 2. 判断是否最小序号节点 3. 若非最小,监听前一个节点变化 #### 2. 代码实现(Curator框架) ```xml <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> </dependency> ``` 配置类: ```java @Configuration public class ZkConfig { @Value("${zookeeper.url}") private String zkUrl; @Bean public CuratorFramework curatorFramework() { return CuratorFrameworkFactory.newClient(zkUrl, new RetryNTimes(5, 1000)); } } ``` 业务使用: ```java public class ZkLockService { @Autowired private CuratorFramework client; public void processWithLock() throws Exception { InterProcessMutex lock = new InterProcessMutex(client, "/lockPath"); if (lock.acquire(10, TimeUnit.SECONDS)) { try { // 业务逻辑 } finally { lock.release(); } } } } ``` > 特性:会话断开自动释放,避免死[^5] ### 三、方案对比 | 特性 | Redis | Zookeeper | |--------------------|-------------------------------|---------------------------| | 性能 | 10万+ QPS | 1万-5万 QPS | | 可靠性 | 依赖持久化机制 | 原生CP模型保证[^1] | | 释放方 | 超时自动释放 | 会话结束自动释放 | | 实现复杂度 | 需处理原子性问题 | 原生支持协调 | | 适用场景 | 高频短时操作 | 强一致性要求的场景 | ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值