分布式锁(Redisson)
1. 为什么需要分布式锁
Section titled “1. 为什么需要分布式锁”单机环境下,synchronized 和 ReentrantLock 可以保证线程安全。但在分布式部署时,多个服务实例各自的 JVM 锁互不感知,无法保证跨实例的互斥访问。
单机(JVM 锁有效):实例 A ──▶ synchronized(this) ──▶ 安全
分布式(JVM 锁失效):实例 A ──▶ synchronized(this) ──┐ ├──▶ 同时操作同一资源 ❌实例 B ──▶ synchronized(this) ──┘(两个 JVM 的锁互不影响)分布式锁需要一个所有实例共享的外部存储来协调,Redis 是最常用的选择。
2. 手写 Redis 锁的问题
Section titled “2. 手写 Redis 锁的问题”直接用 SET NX EX 实现分布式锁,存在诸多边界问题:
// 看似简单,实则问题很多Boolean locked = redisTemplate.opsForValue() .setIfAbsent("lock:order", "1", 30, TimeUnit.SECONDS);| 问题 | 描述 |
|---|---|
| 锁超时续期 | 业务执行超过 30 秒,锁自动过期,其他实例获得锁,产生并发 |
| 误删他人锁 | A 的锁过期后 B 获得锁,A 执行完后把 B 的锁删了 |
| 删锁非原子 | 判断锁是自己的 + 删除锁,两步非原子,中间可能被抢占 |
| 可重入性 | 同一线程再次获取同一把锁会死锁 |
| 主从切换丢锁 | Master 写锁后宕机,Slave 提升为 Master,锁丢失 |
Redisson 将以上问题全部解决,生产环境应直接使用 Redisson。
3. Redisson 简介
Section titled “3. Redisson 简介”Redisson 是基于 Redis 的 Java 分布式组件库,提供了分布式锁、信号量、限流器等丰富的分布式工具,底层使用 Lua 脚本保证原子性,并通过 WatchDog(看门狗) 机制自动续期。
Redisson 分布式锁核心特性:├── 自动续期(WatchDog):锁未释放前每隔 leaseTime/3 自动续期├── 可重入:同一线程可多次获取同一把锁├── 锁所有权校验:只有加锁线程才能释放锁└── Lua 脚本保证原子性:加锁/释放锁均为原子操作4. 快速集成
Section titled “4. 快速集成”4.1. 添加依赖
Section titled “4.1. 添加依赖”<dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.27.0</version></dependency>4.2. 配置
Section titled “4.2. 配置”spring: data: redis: host: localhost port: 6379 password: 123456Redisson Starter 会自动读取 Spring Redis 配置创建 RedissonClient Bean,无需额外配置。
如需自定义配置:
@Beanpublic RedissonClient redissonClient() { Config config = new Config(); config.useSingleServer() .setAddress("redis://localhost:6379") .setPassword("123456") .setConnectionPoolSize(64) .setConnectionMinimumIdleSize(10); return Redisson.create(config);}5. RLock — 可重入锁
Section titled “5. RLock — 可重入锁”5.1. 基础用法
Section titled “5.1. 基础用法”@Autowiredprivate RedissonClient redissonClient;
public void createOrder(Long orderId) { RLock lock = redissonClient.getLock("lock:order:" + orderId);
lock.lock(); // 加锁(默认 leaseTime = -1,WatchDog 自动续期) try { // 业务逻辑 doCreateOrder(orderId); } finally { lock.unlock(); // 释放锁(必须在 finally 中) }}5.2. 指定锁超时时间(关闭 WatchDog)
Section titled “5.2. 指定锁超时时间(关闭 WatchDog)”// leaseTime > 0 时 WatchDog 不工作,到时间自动释放lock.lock(10, TimeUnit.SECONDS);5.3. 尝试获取锁(tryLock)
Section titled “5.3. 尝试获取锁(tryLock)”// waitTime:等待获取锁的最长时间// leaseTime:锁的持有时间(-1 = WatchDog 自动续期)boolean acquired = lock.tryLock(3, -1, TimeUnit.SECONDS);
if (acquired) { try { doCreateOrder(orderId); } finally { lock.unlock(); }} else { // 未获得锁,直接返回或抛异常 throw new RuntimeException("系统繁忙,请稍后重试");}5.4. 可重入特性
Section titled “5.4. 可重入特性”RLock lock = redissonClient.getLock("lock:order:1001");
lock.lock(); // 加锁 1 次,计数 = 1try { lock.lock(); // 同一线程再次加锁,计数 = 2,不会死锁 try { doWork(); } finally { lock.unlock(); // 释放 1 次,计数 = 1 }} finally { lock.unlock(); // 再释放 1 次,计数 = 0,锁完全释放}6. WatchDog 自动续期原理
Section titled “6. WatchDog 自动续期原理”WatchDog 工作流程:
lock.lock() 加锁成功(默认 leaseTime = 30 秒) │ ▼WatchDog 启动定时任务(每 10 秒执行一次,即 30/3) │ ├── 检查锁是否仍被当前线程持有 │ │ │ ├── 是 ──▶ 重置过期时间为 30 秒(续期) │ └── 否 ──▶ 停止续期任务 │ ▼lock.unlock() 释放锁 ──▶ WatchDog 停止Redis 中锁的数据结构(Hash):
key: lock:order:1001type: Hashfield: <UUID>:<线程ID> ← 标识锁的持有者(UUID 区分实例,线程ID 支持可重入)value: 2 ← 重入次数ttl: 30s ← 每次续期重置7. 其他锁类型
Section titled “7. 其他锁类型”7.1. 公平锁(FairLock)
Section titled “7.1. 公平锁(FairLock)”按请求顺序依次获取锁,防止线程饥饿:
RLock fairLock = redissonClient.getFairLock("lock:order");fairLock.lock();7.2. 读写锁(ReadWriteLock)
Section titled “7.2. 读写锁(ReadWriteLock)”读读不互斥,读写/写写互斥,适合读多写少场景:
RReadWriteLock rwLock = redissonClient.getReadWriteLock("lock:config");
// 读操作(共享锁)RLock readLock = rwLock.readLock();readLock.lock();try { readConfig();} finally { readLock.unlock();}
// 写操作(独占锁)RLock writeLock = rwLock.writeLock();writeLock.lock();try { updateConfig();} finally { writeLock.unlock();}7.3. 联锁(MultiLock)
Section titled “7.3. 联锁(MultiLock)”同时锁住多个资源,全部加锁成功才算获得锁:
RLock lock1 = redissonClient.getLock("lock:account:A");RLock lock2 = redissonClient.getLock("lock:account:B");
RLock multiLock = redissonClient.getMultiLock(lock1, lock2);multiLock.lock();try { transfer(accountA, accountB, amount);} finally { multiLock.unlock();}8. 封装锁注解(生产实践)
Section titled “8. 封装锁注解(生产实践)”// 自定义注解@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface DistributedLock { String key(); // SpEL 表达式,如 "#orderId" String prefix() default "lock:"; // key 前缀 long waitTime() default 3; // 等待时间(秒) long leaseTime() default -1; // 持有时间(-1 = WatchDog) TimeUnit unit() default TimeUnit.SECONDS;}// AOP 切面@Aspect@Component@RequiredArgsConstructor@Slf4jpublic class DistributedLockAspect {
private final RedissonClient redissonClient; private final ExpressionParser parser = new SpelExpressionParser();
@Around("@annotation(distributedLock)") public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
// 解析 SpEL key String keyExpr = distributedLock.key(); String keyVal = parseSpel(keyExpr, joinPoint); String lockKey = distributedLock.prefix() + keyVal;
RLock lock = redissonClient.getLock(lockKey); boolean acquired = lock.tryLock( distributedLock.waitTime(), distributedLock.leaseTime(), distributedLock.unit() );
if (!acquired) { throw new RuntimeException("获取锁失败,请稍后重试:" + lockKey); } try { return joinPoint.proceed(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } }
private String parseSpel(String expr, ProceedingJoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); String[] paramNames = signature.getParameterNames(); Object[] args = joinPoint.getArgs();
EvaluationContext context = new StandardEvaluationContext(); for (int i = 0; i < paramNames.length; i++) { context.setVariable(paramNames[i], args[i]); } return parser.parseExpression(expr).getValue(context, String.class); }}// 使用注解@Servicepublic class OrderService {
@DistributedLock(key = "#orderId", prefix = "lock:order:") public void createOrder(Long orderId) { // 自动加锁、自动释放 doCreateOrder(orderId); }
@DistributedLock(key = "#userId + ':' + #productId", prefix = "lock:stock:", waitTime = 5, leaseTime = 10) public void deductStock(Long userId, Long productId, int qty) { doDeductStock(productId, qty); }}9. 分布式锁使用总结
Section titled “9. 分布式锁使用总结”| 场景 | 推荐锁类型 | 关键配置 |
|---|---|---|
| 一般互斥场景 | RLock(可重入锁) | 默认 WatchDog 续期 |
| 读多写少(配置、字典) | RReadWriteLock | 读锁共享,写锁独占 |
| 转账/多资源同时锁 | MultiLock | 全部加锁才算成功 |
| 顺序性要求严格 | FairLock | 按请求顺序分配 |