一、Redisson 分布式锁的工作原理
1. 基本机制
Redisson 实现分布式锁的核心是基于 Redis 的 SET
命令。具体来说,Redisson 使用以下命令来获取锁:
SET resource_name my_random_value NX PX 30000
resource_name
:锁的名称。my_random_value
:每个客户端生成的唯一标识符(通常是 UUID + 线程 ID),用于确保锁只能由持有者释放。NX
:只有当键不存在时才设置键值。PX 30000
:设置键的过期时间为 30 秒(毫秒为单位)。
这种机制确保了即使客户端崩溃或网络故障,锁也会在一定时间后自动释放,避免死锁问题。
2. 看门狗机制
为了防止长时间运行的任务因锁超时而被意外释放,Redisson 提供了“看门狗”机制。当一个线程成功获取锁后,Redisson 会启动一个后台线程,定期检查并延长锁的有效期。默认情况下,锁的有效期为 30 秒,但看门狗会在到期前自动续期,直到任务完成并显式释放锁。
可以通过以下方式禁用看门狗机制:
lock.lock(10, TimeUnit.SECONDS); // 锁持有时间为10秒,不启用看门狗
二、高级功能
1. 可重入锁(Reentrant Lock)
Redisson 支持可重入锁,允许同一个线程多次获取同一把锁而不发生死锁。这对于需要递归调用的方法非常有用。
RLock lock = redisson.getLock("myLock");// 获取锁
lock.lock();
try {// 执行业务逻辑
} finally {// 释放锁lock.unlock();
}
异步获取锁:
lock.lockAsync().thenAccept(result -> {if (result) {try {// 执行业务逻辑} finally {lock.unlockAsync();}}
});
2. 公平锁(Fair Lock)
公平锁按照请求顺序分配锁,避免饥饿现象。即先请求锁的线程优先获得锁。
RFairLock fairLock = redisson.getFairLock("myFairLock");fairLock.lock(); // 获取锁
try {// 执行业务逻辑
} finally {fairLock.unlock(); // 释放锁
}
3. 联锁(MultiLock)
联锁允许多个锁同时生效,只有当所有锁都获取成功时才算获取成功。
RLock lock1 = redisson.getLock("lock1");
RLock lock2 = redisson.getLock("lock2");RedissonMultiLock multiLock = new RedissonMultiLock(lock1, lock2);multiLock.lock(); // 获取联锁
try {// 执行业务逻辑
} finally {multiLock.unlock(); // 释放联锁
}
4. 红锁(RedLock)
红锁在多个独立的 Redis 节点上实现分布式锁,提高系统的可靠性和容错能力。通常需要至少三个节点来保证高可用性。
Config config = new Config();
config.useClusterServers().addNodeAddress("redis://127.0.0.1:6379", "redis://127.0.0.1:6380", "redis://127.0.0.1:6381");RedissonClient redisson = Redisson.create(config);RLock lock1 = redisson.getLock("lock1");
RLock lock2 = redisson.getLock("lock2");
RLock lock3 = redisson.getLock("lock3");RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);redLock.lock(); // 获取红锁
try {// 执行业务逻辑
} finally {redLock.unlock(); // 释放红锁
}
5. 读写锁(ReadWriteLock)
读写锁允许多个读操作同时进行,但写操作互斥。适合多读少写的场景。
RReadWriteLock rwLock = redisson.getReadWriteLock("myRWLock");// 获取读锁
rwLock.readLock().lock();
try {// 执行读操作
} finally {rwLock.readLock().unlock();
}// 获取写锁
rwLock.writeLock().lock();
try {// 执行写操作
} finally {rwLock.writeLock().unlock();
}
三、应用场景
1. 资源竞争
在多个实例或进程中访问共享资源(如文件系统、数据库记录等)时,确保同一时刻只有一个实例进行操作。
2. 定时任务调度
防止多个实例同时执行相同的定时任务,确保任务在同一时间内只执行一次。
3. 缓存一致性
当多个实例尝试更新同一缓存项时,使用分布式锁可以避免数据不一致的问题。
4. 高并发交易处理
在金融系统中,确保同一账户的交易在同一时刻只能被一个进程处理,以保证数据的一致性和准确性。
四、注意事项
1. 锁的过期时间
设置合理的锁过期时间非常重要。过短可能导致业务逻辑未完成时锁已失效,过长则可能导致死锁。可以通过 tryLock(long waitTime, long leaseTime, TimeUnit unit)
方法动态设置等待时间和锁持有时间。
boolean isLocked = lock.tryLock(5, 30, TimeUnit.SECONDS); // 最多等待5秒,锁持有时间为30秒
if (isLocked) {try {// 执行业务逻辑} finally {lock.unlock();}
}
2. 异常处理
在获取锁的过程中可能会抛出 InterruptedException
,需要正确处理中断信号,避免线程处于不明确的状态。确保在 finally
块中释放锁,防止因为异常导致锁未释放。
try {boolean isLocked = lock.tryLock(5, 30, TimeUnit.SECONDS);if (isLocked) {// 执行业务逻辑}
} catch (InterruptedException e) {Thread.currentThread().interrupt();System.err.println("Thread was interrupted while trying to acquire lock.");
} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}
}
3. 网络分区问题
在 Redis 集群环境中,网络分区可能导致部分节点无法正常工作,影响锁的可靠性。使用 Redlock 算法可以在一定程度上缓解这个问题,但不能完全避免。
4. 序列化与反序列化
如果锁的值包含复杂对象,需注意序列化与反序列化的性能和兼容性问题。可以选择合适的序列化器,如 Jackson JSON 序列化器或 Kryo 序列化器。
config.setCodec(new JsonJacksonCodec()); // 设置Jackson JSON序列化器
五、完整示例代码
以下是一个结合上述要点的更完整的示例,展示了如何使用 Redisson 实现分布式锁,并考虑了异常处理和锁的自动续期。
1. 单机模式下的分布式锁
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;public class RedissonDistributedLockExample {public static void main(String[] args) {// 配置Redisson客户端Config config = new Config();config.useSingleServer().setAddress("redis://127.0.0.1:6379").setCodec(new JsonJacksonCodec()); // 设置序列化器RedissonClient redisson = Redisson.create(config);String lockName = "myDistributedLock";RLock lock = redisson.getLock(lockName);try {// 尝试获取锁,最多等待5秒,锁持有时间为30秒boolean isLocked = lock.tryLock(5, 30, java.util.concurrent.TimeUnit.SECONDS);if (isLocked) {System.out.println("Lock acquired successfully.");performBusinessLogic();} else {System.out.println("Failed to acquire lock.");}} catch (InterruptedException e) {Thread.currentThread().interrupt();System.err.println("Thread was interrupted while trying to acquire lock.");} finally {// 确保最终释放锁if (lock.isHeldByCurrentThread()) {lock.unlock();System.out.println("Lock released.");}}// 关闭Redisson客户端redisson.shutdown();}private static void performBusinessLogic() {System.out.println("Executing business logic...");try {// 模拟耗时操作Thread.sleep(10000); // 模拟10秒的操作} catch (InterruptedException e) {Thread.currentThread().interrupt();}}
}
2. 集群模式下的分布式锁(RedLock)
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.redisson.RedissonRedLock;public class RedissonRedLockExample {public static void main(String[] args) {// 配置Redisson客户端Config config = new Config();config.useClusterServers().addNodeAddress("redis://127.0.0.1:6379", "redis://127.0.0.1:6380", "redis://127.0.0.1:6381");RedissonClient redisson = Redisson.create(config);RLock lock1 = redisson.getLock("lock1");RLock lock2 = redisson.getLock("lock2");RLock lock3 = redisson.getLock("lock3");RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);try {// 尝试获取锁,最多等待5秒,锁持有时间为30秒boolean isLocked = redLock.tryLock(5, 30, java.util.concurrent.TimeUnit.SECONDS);if (isLocked) {System.out.println("RedLock acquired successfully.");performBusinessLogic();} else {System.out.println("Failed to acquire RedLock.");}} catch (InterruptedException e) {Thread.currentThread().interrupt();System.err.println("Thread was interrupted while trying to acquire RedLock.");} finally {// 确保最终释放锁if (redLock.isLocked()) {redLock.unlock();System.out.println("RedLock released.");}}// 关闭Redisson客户端redisson.shutdown();}private static void performBusinessLogic() {System.out.println("Executing business logic with RedLock...");try {// 模拟耗时操作Thread.sleep(10000); // 模拟10秒的操作} catch (InterruptedException e) {Thread.currentThread().interrupt();}}
}
六、最佳实践
1. 选择合适的锁类型
根据具体的业务需求选择合适的锁类型(如可重入锁、公平锁、联锁、红锁等)。例如,对于需要递归调用的方法,应使用可重入锁;对于多读少写的场景,应使用读写锁。
2. 合理设置锁的过期时间
锁的过期时间应根据业务逻辑的执行时间合理设置,避免锁超时或死锁问题。可以通过 tryLock(long waitTime, long leaseTime, TimeUnit unit)
方法动态设置等待时间和锁持有时间。
3. 正确处理异常
在获取锁的过程中捕获并处理可能的异常,确保锁能够被正确释放。尤其是在多线程环境下,务必确保锁的释放不会遗漏。
4. 使用序列化器
如果锁的值包含复杂对象,选择合适的序列化器以提高性能和兼容性。常见的序列化器包括 Jackson JSON 序列化器和 Kryo 序列化器。
5. 监控和日志
在生产环境中使用分布式锁时,建议添加适当的监控和日志记录,以便及时发现和解决潜在的问题。例如,记录每次获取锁的时间、锁的状态变化等信息。