Redisson 实现分布式锁

一、Redisson 分布式锁的工作原理

1. 基本机制

Redisson 实现分布式锁的核心是基于 Redis 的 SET 命令。具体来说,Redisson 使用以下命令来获取锁:

bash 复制代码
SET resource_name my_random_value NX PX 30000
  • resource_name:锁的名称。
  • my_random_value:每个客户端生成的唯一标识符(通常是 UUID + 线程 ID),用于确保锁只能由持有者释放。
  • NX:只有当键不存在时才设置键值。
  • PX 30000:设置键的过期时间为 30 秒(毫秒为单位)。

这种机制确保了即使客户端崩溃或网络故障,锁也会在一定时间后自动释放,避免死锁问题。

2. 看门狗机制

为了防止长时间运行的任务因锁超时而被意外释放,Redisson 提供了"看门狗"机制。当一个线程成功获取锁后,Redisson 会启动一个后台线程,定期检查并延长锁的有效期。默认情况下,锁的有效期为 30 秒,但看门狗会在到期前自动续期,直到任务完成并显式释放锁。

可以通过以下方式禁用看门狗机制:

java 复制代码
lock.lock(10, TimeUnit.SECONDS); // 锁持有时间为10秒,不启用看门狗

二、高级功能

1. 可重入锁(Reentrant Lock)

Redisson 支持可重入锁,允许同一个线程多次获取同一把锁而不发生死锁。这对于需要递归调用的方法非常有用。

java 复制代码
RLock lock = redisson.getLock("myLock");

// 获取锁
lock.lock();
try {
    // 执行业务逻辑
} finally {
    // 释放锁
    lock.unlock();
}

异步获取锁:

java 复制代码
lock.lockAsync().thenAccept(result -> {
    if (result) {
        try {
            // 执行业务逻辑
        } finally {
            lock.unlockAsync();
        }
    }
});
2. 公平锁(Fair Lock)

公平锁按照请求顺序分配锁,避免饥饿现象。即先请求锁的线程优先获得锁。

java 复制代码
RFairLock fairLock = redisson.getFairLock("myFairLock");

fairLock.lock(); // 获取锁
try {
    // 执行业务逻辑
} finally {
    fairLock.unlock(); // 释放锁
}
3. 联锁(MultiLock)

联锁允许多个锁同时生效,只有当所有锁都获取成功时才算获取成功。

java 复制代码
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 节点上实现分布式锁,提高系统的可靠性和容错能力。通常需要至少三个节点来保证高可用性。

java 复制代码
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)

读写锁允许多个读操作同时进行,但写操作互斥。适合多读少写的场景。

java 复制代码
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) 方法动态设置等待时间和锁持有时间。

java 复制代码
boolean isLocked = lock.tryLock(5, 30, TimeUnit.SECONDS); // 最多等待5秒,锁持有时间为30秒
if (isLocked) {
    try {
        // 执行业务逻辑
    } finally {
        lock.unlock();
    }
}
2. 异常处理

在获取锁的过程中可能会抛出 InterruptedException,需要正确处理中断信号,避免线程处于不明确的状态。确保在 finally 块中释放锁,防止因为异常导致锁未释放。

java 复制代码
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 序列化器。

java 复制代码
config.setCodec(new JsonJacksonCodec()); // 设置Jackson JSON序列化器

五、完整示例代码

以下是一个结合上述要点的更完整的示例,展示了如何使用 Redisson 实现分布式锁,并考虑了异常处理和锁的自动续期。

1. 单机模式下的分布式锁
java 复制代码
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)
java 复制代码
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. 监控和日志

在生产环境中使用分布式锁时,建议添加适当的监控和日志记录,以便及时发现和解决潜在的问题。例如,记录每次获取锁的时间、锁的状态变化等信息。

相关推荐
数据智能老司机24 分钟前
CockroachDB权威指南——SQL调优
数据库·分布式·架构
数据智能老司机26 分钟前
CockroachDB权威指南——应用设计与实现
数据库·分布式·架构
数据智能老司机39 分钟前
CockroachDB权威指南——CockroachDB 模式设计
数据库·分布式·架构
数据智能老司机19 小时前
CockroachDB权威指南——CockroachDB SQL
数据库·分布式·架构
数据智能老司机20 小时前
CockroachDB权威指南——开始使用
数据库·分布式·架构
Kagol20 小时前
macOS 和 Windows 操作系统下如何安装和启动 MySQL / Redis 数据库
redis·后端·mysql
数据智能老司机20 小时前
CockroachDB权威指南——CockroachDB 架构
数据库·分布式·架构
IT成长日记21 小时前
【Kafka基础】Kafka工作原理解析
分布式·kafka
hzulwy21 小时前
Redis常用的数据结构及其使用场景
数据库·redis
ashane13141 天前
Redis 哨兵集群(Sentinel)与 Cluster 集群对比
redis