Redisson 分布式锁全面解析
一、Redisson 分布式锁原理
Redisson 分布式锁基于 Redis 实现,核心机制如下:
Lua 脚本保证原子性
使用 Lua 脚本在 Redis 中执行锁的获取和释放操作,确保多个 Redis 命令的原子性。可重入锁设计
同一线程可重复获取锁,通过计数器记录重入次数,避免死锁。看门狗(Watchdog)自动续期
后台线程定期检查锁状态,若锁仍被持有且未完成业务逻辑,则自动延长锁的过期时间(默认续期到 30 秒)。锁释放与超时机制
锁设置超时时间(默认 30 秒),即使客户端崩溃,锁也会自动释放,防止死锁。
二、Redisson 分布式锁的实现方式
1. 引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.17.0</version>
</dependency>
2. 配置 Redisson 客户端
Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setPassword("password");
RedissonClient redisson = Redisson.create(config);
3. 获取锁实例
RLock lock = redisson.getLock("myLock");
三、Redisson 分布式锁的 Java 方法及作用
方法 | 作用 | 使用场景 |
---|---|---|
void lock() |
阻塞式获取锁,直到成功或线程中断。 | 必须获取锁的场景(如关键资源互斥访问) |
void lock(long leaseTime, TimeUnit unit) |
获取锁并指定锁自动释放时间(不触发看门狗续期)。 | 明确锁持有时间的场景 |
boolean tryLock() |
非阻塞尝试获取锁,成功返回 true ,否则立即返回 false 。 |
快速失败逻辑 |
boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) |
在 waitTime 内尝试获取锁,成功返回 true ,超时返回 false ,并指定锁自动释放时间。 |
允许等待但限制时间的场景 |
void unlock() |
释放锁。 | 必须放在 finally 块中释放锁 |
boolean isLocked() |
判断锁是否被任何线程持有。 | 监控锁状态 |
boolean isHeldByCurrentThread() |
判断当前线程是否持有锁。 | 防止其他线程误释放锁 |
四、不同锁类型及适用场景
锁类型 | 特点 | 适用场景 |
---|---|---|
可重入锁(Reentrant Lock) | 同一线程可多次获取锁,通过计数器实现。 | 递归调用或需要重复获取锁的业务逻辑 |
公平锁(Fair Lock) | 按请求顺序分配锁,避免线程饥饿。 | 需要公平性的场景(如资源按顺序分配) |
联锁(MultiLock) | 同时获取多个锁,所有锁都成功才算成功。 | 需要同时锁定多个资源的场景(如分布式事务) |
红锁(RedLock) | 在多个独立的 Redis 节点上获取锁,多数成功才算成功。 | 高可用性要求严格的场景(如金融交易) |
读写锁(ReadWriteLock) | 读锁共享,写锁互斥。 | 读多写少的场景(如缓存数据更新) |
五、防止死锁与超时的策略
自动续期(看门狗机制)
- 默认情况下,若未显式指定
leaseTime
,Redisson 会启动看门狗线程,每 10 秒续期锁至 30 秒。 - 代码示例:
lock.lock(); // 自动续期
- 默认情况下,若未显式指定
设置锁超时时间
- 显式指定
leaseTime
,锁在超时后自动释放,避免死锁。 - 代码示例:
lock.lock(10, TimeUnit.SECONDS); // 10秒后自动释放
- 显式指定
使用
tryLock
控制等待时间- 避免线程长时间阻塞,设置最大等待时间。
- 代码示例:
if (lock.tryLock(3, 10, TimeUnit.SECONDS)) { // 等待3秒,锁持有10秒 // 业务逻辑 }
强制释放锁
- 在
finally
块中释放锁,确保异常情况下锁能被释放。 - 代码示例:
try { lock.lock(); // 业务逻辑 } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } }
- 在
红锁(RedLock)防脑裂
- 在多个独立 Redis 节点上获取锁,半数以上成功才算有效。
- 代码示例:
RLock lock1 = redisson1.getLock("lock"); RLock lock2 = redisson2.getLock("lock"); RLock lock3 = redisson3.getLock("lock"); RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3); redLock.lock();
六、不同场景下的锁选择建议及代码示例
1. 简单互斥场景
- 场景描述:秒杀扣减库存,确保同一商品不会被超卖。
- 锁类型:可重入锁(
RLock
)。 - 方法:
lock()
+unlock()
。 - 代码示例:
RLock lock = redisson.getLock("product_lock_" + productId); try { // 阻塞式获取锁,直到成功 lock.lock(); // 执行库存扣减逻辑 int stock = productService.getStock(productId); if (stock > 0) { productService.reduceStock(productId); } } finally { // 确保只有持有锁的线程才能释放 if (lock.isHeldByCurrentThread()) { lock.unlock(); } }
2. 高并发读场景
- 场景描述:缓存数据读取,允许多线程并发读,但写时互斥。
- 锁类型:读写锁(
RReadWriteLock
)。 - 方法:
readLock().lock()
+readLock().unlock()
。 - 代码示例:
RReadWriteLock rwLock = redisson.getReadWriteLock("cache_lock"); RLock readLock = rwLock.readLock(); try { readLock.lock(); // 从缓存读取数据(允许多线程并发读) String data = cacheService.get("key"); if (data == null) { // 若缓存未命中,获取写锁更新 RLock writeLock = rwLock.writeLock(); try { writeLock.lock(); data = dbService.loadFromDB("key"); cacheService.put("key", data); } finally { writeLock.unlock(); } } return data; } finally { readLock.unlock(); }
3. 多资源原子操作
- 场景描述:转账操作需同时锁定转出和转入账户。
- 锁类型:联锁(
RedissonMultiLock
)。 - 方法:同时锁定多个资源。
- 代码示例:
// 获取两个账户的锁 RLock lockA = redisson.getLock("account_lock_" + fromAccountId); RLock lockB = redisson.getLock("account_lock_" + toAccountId); RedissonMultiLock multiLock = new RedissonMultiLock(lockA, lockB); try { // 同时锁定两个账户的锁 multiLock.lock(); // 执行转账逻辑 accountService.transfer(fromAccountId, toAccountId, amount); } finally { multiLock.unlock(); }
4. 高可用性要求
场景描述:支付系统订单处理,需严格防止锁失效导致重复支付。
锁类型:红锁(
RedissonRedLock
)。方法:在多个独立 Redis 节点上获取锁。
代码示例:
// 连接三个独立的 Redis 实例 RedissonClient redisson1 = createClient("redis://node1:6379"); RedissonClient redisson2 = createClient("redis://node2:6379"); RedissonClient redisson3 = createClient("redis://node3:6379"); RLock lock1 = redisson1.getLock("order_lock_" + orderId); RLock lock2 = redisson2.getLock("order_lock_" + orderId); RLock lock3 = redisson3.getLock("order_lock_" + orderId); RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3); try { // 在多数节点上获取锁 redLock.lock(); // 处理支付订单 paymentService.process(orderId); } finally { redLock.unlock(); }
5. 严格顺序处理要求
场景描述:以 订单支付系统 为例,要求严格按照用户提交订单的时间顺序处理支付请求。
锁类型:公平锁(
RedissonFairLock
)。方法:按照线程请求锁的顺序分配锁,确保先到先得,避免线程饥饿。
代码示例:
获取公平锁实例
// 获取公平锁实例 RLock fairLock = redisson.getFairLock("order_payment_lock");
使用公平锁处理订单
public class OrderService { private final RLock fairLock; public OrderService(RedissonClient redisson) { this.fairLock = redisson.getFairLock("order_payment_lock"); } public void processPayment(String orderId) { try { // 获取公平锁(按请求顺序排队) fairLock.lock(); // 模拟支付处理(关键业务逻辑) System.out.println("处理订单: " + orderId + " | 线程: " + Thread.currentThread().getName()); Thread.sleep(500); // 模拟业务耗时 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { if (fairLock.isHeldByCurrentThread()) { fairLock.unlock(); } } } }
模拟多线程请求
public static void main(String[] args) { RedissonClient redisson = Redisson.create(); OrderService orderService = new OrderService(redisson); // 模拟10个订单按顺序提交 for (int i = 1; i <= 10; i++) { String orderId = "ORDER-" + i; new Thread(() -> orderService.processPayment(orderId)).start(); // 控制线程启动间隔,模拟请求顺序到达 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } }
输出结果
处理订单: ORDER-1 | 线程: Thread-0 处理订单: ORDER-2 | 线程: Thread-1 处理订单: ORDER-3 | 线程: Thread-2 处理订单: ORDER-4 | 线程: Thread-3 ... 处理订单: ORDER-10 | 线程: Thread-9
观察结果:订单严格按照 ORDER-1 到 ORDER-10 的顺序处理,线程按启动顺序获取锁
代码示例关键点说明
- 锁的命名:
- 锁的 Key 需具备唯一性和业务语义(如
product_lock_1001
)。
- 锁的 Key 需具备唯一性和业务语义(如
- 锁释放的可靠性:
- 必须在
finally
块中释放锁,并通过isHeldByCurrentThread()
防止误释放。
- 必须在
- 读写锁的升级:
- 读锁升级为写锁时,需先释放读锁再获取写锁(Redisson 自动处理)。
- 红锁的节点独立性:
- 红锁要求 Redis 节点互相独立(非主从或集群模式)。
总结
通过具体场景的代码示例,可以看出 Redisson 分布式锁的灵活性和强大功能:
- 简单互斥:直接使用
RLock
实现原子操作。 - 读写分离:通过
RReadWriteLock
提升读性能。 - 多资源协同:利用
RedissonMultiLock
保证多资源操作的原子性。 - 高可用场景:通过
RedissonRedLock
防止 Redis 单点故障。
每个场景的代码均遵循以下原则:
- 最小化锁范围:仅锁定必要资源。
- 异常处理:确保锁最终释放。
- 可维护性:锁 Key 具备业务含义,便于监控和调试。
七、异常处理最佳实践
锁释放失败
- 原因:未在
finally
块释放锁或线程中断。 - 解决方案:确保
unlock()
在finally
中执行,并检查当前线程是否持有锁。
- 原因:未在
锁超时与业务执行时间冲突
- 原因:业务逻辑执行时间超过锁超时时间。
- 解决方案:调整锁超时时间或优化业务逻辑。
网络分区与 Redis 故障
- 原因:Redis 节点不可用导致锁状态不一致。
- 解决方案:使用红锁(RedLock)或集群模式。
总结
Redisson 提供了丰富的分布式锁实现,核心在于 原子性操作、可重入设计 和 自动续期机制。选择锁类型时需根据场景权衡性能、可用性和复杂度。关键注意事项包括:
- 始终在
finally
块中释放锁。 - 合理设置锁超时时间,避免业务未完成锁已过期。
- 高可用场景使用红锁,防止 Redis 单点故障。
- 读写分离场景使用读写锁,提升并发性能。
通过合理使用 Redisson 分布式锁,可有效解决分布式系统中的资源竞争问题,保障数据一致性。