嘿,小伙伴们,我是黑马彭于晏。
今天我们来聊聊那些让人头大的分布式锁。
想象一下,多个服务实例并发访问共享资源,没有合适的锁机制,数据的一致性怎么保证?
别怕,我有秘籍——lock4j,让你秒变高手,轻松解锁新技能!
问题演示
在没有统一解决方案前,我们的代码可能是这样的:
Redis分布式锁伪代码
public void safeUpdateWithRedis(String key, Runnable transfer) {
try {
// 使用SETNX命令尝试获取锁,锁10秒自动释放
Boolean lock = redisTemplate.opsForValue().setIfAbsent(key,"1",10, TimeUnit.SECONDS);
if (!lock) {
// 获取锁失败,稍后重试
return;
}
// 执行转账操作
transfer.run();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 释放锁
redisTemplate.delete(key);
}
}
Redisson分布式锁伪代码
public void safeUpdateWithRedisson(String key, Runnable transfer) {
// 获取锁对象
RLock lock = redissonClient.getLock(key);
try {
// 尝试获取锁,最多等待3秒,锁10秒自动释放
if (!lock.tryLock(3, 10, TimeUnit.SECONDS)) {
// 获取锁失败,稍后重试
return;
}
// 执行转账操作
transfer.run();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 释放锁
lock.unlock();
}
}
Zookeeper分布式锁伪代码
public void safeUpdateWithZookeeper(String path, Runnable transfer) throws Exception {
// 创建一个临时顺序节点
CuratorFramework client = CuratorFrameworkFactory.newClient(zookeeperServers);
client.start();
// 创建一个基于ZooKeeper的临时顺序节点的互斥锁
InterProcessMutex mutex = new InterProcessMutex(client, path);
try {
// 尝试获取互斥锁。
mutex.acquire();
// 执行转账操作
transfer.run();
} finally {
// 确保互斥锁被释放,无论前面的代码是否抛出异常
mutex.release();
client.close();
}
}
可以看到,每种技术的实现方式都不同,代码难以复用,这无疑增加了开发和维护的难度。
问题分析
问题主要体现在以下几个方面:
- 技术栈差异:不同的存储系统,如Redis、Redisson、Zookeeper,它们API设计不同,导致实现方式也不同。
- 学习成本:每种技术都需要投入时间去学习其API和使用方式。
- 代码维护:多套实现,增加了代码的复杂性,难以统一管理和维护。
- 性能考虑:不同的实现可能有不同的性能表现,需要根据业务场景做出选择。
深入探究
为什么分布式锁的实现会如此复杂?这主要是因为:
- 缺乏统一的抽象层:不同的技术实现方式缺乏统一的抽象,导致代码难以复用。
- 环境差异:不同的部署环境可能需要使用不同的技术栈,增加了实现的复杂性。
- 业务场景多样性:不同的业务场景对锁的性能和可靠性要求不同,需要灵活选择实现方式。
解决方案
lock4j的出现,就是为了解决这些问题。它提供了一个统一的抽象层,支持多种存储系统作为底层实现,让我们可以用一套代码应对不同技术栈。
- 引入依赖
在项目的pom.xml中加入lock4j的依赖(支持同时存在,不同方法不同锁实现):
<dependencies>
<!--若使用redisTemplate作为分布式锁底层,则需要引入-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redis-template-spring-boot-starter</artifactId>
<version>${latest.version}</version>
</dependency>
<!--若使用redisson作为分布式锁底层,则需要引入-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
<version>${latest.version}</version>
</dependency>
<!--若使用zookeeper作为分布式锁底层,则需要引入-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-zookeeper-spring-boot-starter</artifactId>
<version>${latest.version}</version>
</dependency>
</dependencies>
- 配置
根据你的底层实现,配置Redis或Zookeeper的连接信息。
spring:
redis:
host: 127.0.0.1
port: 6379
coordinate:
zookeeper:
zkServers: 127.0.0.1:2181
- 使用注解
在需要加锁的方法上使用@Lock4j注解:
@Service
public class DemoService {
//默认获取锁超时3秒,30秒锁过期
@Lock4j
public void simple() {
//do something
}
//完全配置,支持spel
@Lock4j(keys = {"#user.id", "#user.name"}, expire = 60000, acquireTimeout = 1000)
public User customMethod(User user) {
return user;
}
}
高级使用
全局配置
lock4j支持全局配置锁的基本参数:
lock4j:
acquire-timeout: 3000 #默认值3s,可不设置
expire: 30000 #默认值30s,可不设置
primary-executor: com.baomidou.lock.executor.RedisTemplateLockExecutor #默认redisson>redisTemplate>zookeeper,可不设置
lock-key-prefix: lock4j #锁key前缀, 默认值lock4j,可不设置
手动上锁解锁
lock4j也支持编程式上锁解锁:
@Service
public class ProgrammaticService {
@Autowired
private LockTemplate lockTemplate;
public void programmaticLock(String userId) {
// 各种查询操作 不上锁
// ...
// 获取锁
final LockInfo lockInfo = lockTemplate.lock(userId, 30000L, 5000L, RedissonLockExecutor.class);
if (null == lockInfo) {
throw new RuntimeException("业务处理中,请稍后再试");
}
// 获取锁成功,处理业务
try {
System.out.println("执行简单方法1 , 当前线程:" + Thread.currentThread().getName() + " , counter:" + (counter++));
} finally {
//释放锁
lockTemplate.releaseLock(lockInfo);
}
//结束
}
}
lock4j还支持自定义执行器、锁key生成器等高级特性,以满足更复杂的业务需求。
感兴趣的小伙伴可以去看下gitee的lock4j高级使用
实现原理
小伙伴们,接下来我们来深入探索一下lock4j的实现原理,尤其是它如何巧妙地结合自定义注解和面向切面编程(AOP)来实现分布式锁的功能。
lock4j实现原理:自定义注解+AOP
lock4j
的实现原理可以概括为以下几个步骤:
- 自定义注解:
lock4j
定义了一个自定义注解@Lock4j
,用于标识需要加锁的方法。 - AOP切面:通过AOP切面,拦截所有使用了
@Lock4j
注解的方法调用。 - 锁管理器:在切面中,根据注解的参数和全局配置,调用锁管理器来获取和释放锁。
- 执行器:使用不同的执行器来适应不同的底层存储系统,如Redisson、RedisTemplate或Zookeeper。
自定义注解@Lock4j
@Lock4j
注解是lock4j
的核心,它允许开发者轻松地在方法上添加锁:
@Lock4j(keys = {"#orderId"}, expire = 60000, acquireTimeout = 1000)
public void processOrder(String orderId) {
// 业务逻辑
}
AOP切面
lock4j
使用Spring AOP来创建一个切面,该切面拦截所有标记了@Lock4j
注解的方法。在方法执行前后,切面会处理锁的获取和释放。
锁管理器
锁管理器负责实际的锁获取和释放逻辑。它根据注解中的参数(如锁的key、超时时间等)来执行相应的操作。
执行器
执行器是lock4j
中一个重要的组件,它定义了如何与底层存储系统交互。lock4j
支持多种执行器,以适应不同的存储系统。
流程图
以下是lock4j
实现分布式锁的流程图:
graph TD;
A[开始方法执行] --> B[AOP 拦截方法];
B --> C{检查分布式锁};
C -->|锁存在| G[等待或重试];
C -->|锁不存在| D[获取分布式锁];
D -->|成功| E[执行业务逻辑];
D -->|失败| F[处理锁获取失败];
E --> H[执行完毕后释放锁];
H --> I[结束方法执行];
F --> H;
G --> C;
style A fill:#f9d6c1,stroke:#333,stroke-width:4px
style B fill:#f9d6c1,stroke:#333,stroke-width:4px
style C fill:#f9d6c1,stroke:#333,stroke-width:4px
style D fill:#f9d6c1,stroke:#333,stroke-width:4px
style E fill:#f9d6c1,stroke:#333,stroke-width:4px
style F fill:#f9d6c1,stroke:#333,stroke-width:4px
style G fill:#f9d6c1,stroke:#333,stroke-width:4px
style H fill:#f9d6c1,stroke:#333,stroke-width:4px
style I fill:#f9d6c1,stroke:#333,stroke-width:4px
总结
通过lock4j,我们不仅简化了分布式锁的实现,还提供了丰富的高级特性,让我们可以更加灵活地应对各种复杂的业务场景。5分钟,一套代码,优雅地实现分布式锁,这就是lock4j的魅力!
如果你喜欢这篇文章,别忘了点赞、收藏、转发,分享给更多需要的小伙伴。我们下期再见!