Redis分布式锁详细实现演进与Redisson深度解析

Redis分布式锁详细实现演进与Redisson深度解析

一、Redis分布式锁演进历程

1. 基础版:SETNX + EXPIRE(已废弃,存在严重问题)

第1步:尝试获取锁,返回1表示成功,0表示锁已被占用

bash 复制代码
SETNX lock:order 1

第2步:如果上一步成功,设置锁的过期时间为30秒
EXPIRE lock:order 30

问题分析:

非原子性:两个命令分开执行,如果在SETNX后客户端崩溃,EXPIRE不会执行,锁永远不会释放,导致死锁

无持有者标识:任何客户端都能释放锁,可能导致误删

无法区分不同客户端:不知道锁被谁持有

2. 推荐版:原子SET(当前主流基础方案)

bash 复制代码
# 单命令原子操作:获取锁 + 设置过期时间 + 设置持有者标识
SET lock:order client_001 NX PX 30000

代码详解:

lock:order:锁的key,标识业务类型

client_001:锁的value,通常是客户端唯一标识(如UUID+线程ID)

NX:仅当key不存在时才设置(Not eXists)

PX 30000:设置过期时间为30000毫秒(30秒)

优势:

原子性:单个命令,要么全部成功要么全部失败

避免死锁:自动过期机制保证锁最终会释放

持有者标识:value可用于验证锁的持有者

但仍有问题:

锁续期问题:业务执行时间超过30秒时,锁会提前释放

误释放风险:需要额外逻辑保证只有持有者能释放锁

二、Redisson生产级实现深度解析

1. Redisson入门使用

java 复制代码
// 初始化Redisson客户端
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);

// 获取锁对象(非阻塞)
RLock lock = redisson.getLock("order_lock");

// 尝试加锁(支持等待时间)
boolean isLocked = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (isLocked) {
    try {
        // 业务逻辑
        processOrder();
    } finally {
        // 必须确保释放锁
        lock.unlock();
    }
}

2. Redisson核心特性详解

特性1:可重入锁(Reentrant Lock)

java 复制代码
// 获取可重入锁
RLock lock = redisson.getLock("anyLock");

// 第一次加锁
lock.lock(10, TimeUnit.SECONDS);

// 同一个线程内可以再次加锁(重入)
lock.lock(5, TimeUnit.SECONDS);
// ... 执行一些操作

// 需要解锁两次
lock.unlock();
lock.unlock();

实现原理:

使用Redis的Hash结构存储锁信息:key: lock_name, field: client_uuid:thread_id, value: 重入次数。

每次重入时,将重入次数加1。

释放锁时,重入次数减1,直到为0才真正删除锁。

特性2:公平锁(Fair Lock)

java 复制代码
// 获取公平锁
RLock fairLock = redisson.getFairLock("fairLock");

// 加锁
fairLock.lock();

try {
    // 公平锁保证按照请求顺序获取锁
    // 先请求的客户端先获取锁
} finally {
    fairLock.unlock();
}

实现原理:

客户端请求锁时,在Redis中创建一个顺序节点(sorted set)。

按照节点顺序获取锁,先创建的节点先获取。

使用发布订阅机制通知下一个等待的客户端。

代价:性能比普通锁低,因为需要维护顺序和通知机制。

特性3:联锁(MultiLock)

java 复制代码
// 创建多个锁对象
RLock lock1 = redisson.getLock("lock1");
RLock lock2 = redisson.getLock("lock2");
RLock lock3 = redisson.getLock("lock3");

// 创建联锁,将多个锁关联在一起
RedissonMultiLock multiLock = new RedissonMultiLock(lock1, lock2, lock3);

// 获取联锁:必须所有锁都成功获取才算成功
multiLock.lock();

try {
    // 需要同时锁定多个资源时使用
    // 比如:转账需要同时锁定A账户和B账户
    transfer(accountA, accountB, amount);
} finally {
    multiLock.unlock();
}

使用场景:

分布式事务:需要同时锁定多个资源。

跨资源操作:如同时修改用户表和订单表。

避免死锁:按固定顺序获取多个锁(Redisson内部处理)。

特性4:红锁(RedLock)

java 复制代码
// 创建多个独立的Redisson客户端(连接到不同Redis实例)
RedissonClient client1 = createClient("redis://node1:6379");
RedissonClient client2 = createClient("redis://node2:6379");
RedissonClient client3 = createClient("redis://node3:6379");
RedissonClient client4 = createClient("redis://node4:6379");
RedissonClient client5 = createClient("redis://node5:6379");

// 从每个客户端获取锁对象
RLock lock1 = client1.getLock("lock");
RLock lock2 = client2.getLock("lock");
RLock lock3 = client3.getLock("lock");
RLock lock4 = client4.getLock("lock");
RLock lock5 = client5.getLock("lock");

// 创建红锁
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3, lock4, lock5);

// 尝试获取红锁
boolean isLocked = redLock.tryLock(1000, 30000, TimeUnit.MILLISECONDS);
if (isLocked) {
    try {
        // 业务逻辑
    } finally {
        redLock.unlock();
    }
}

红锁算法原理:

1、获取当前时间(毫秒级精度)

2、依次尝试从N个独立的Redis实例获取锁

使用相同的key和value

设置较短的超时时间(远小于锁自动释放时间)

3、计算获取锁的总耗时

当前时间减去步骤1的时间

4、检查是否获取成功

成功条件:从大多数(N/2 + 1)个实例获取锁成功,且总耗时小于锁有效时间

5、如果获取失败,则在所有实例上释放锁

红锁争议:

优点:提高可用性,单个Redis实例故障不影响锁服务

缺点:性能下降,实现复杂,仍有极端情况下的锁安全问题

建议:仅在需要极高可用性且接受复杂性的场景使用

3. Redisson内部实现机制

看门狗(WatchDog)机制

java 复制代码
// Redisson加锁时自动启动看门狗线程
lock.lock(); // 不指定leaseTime,启用看门狗

// 指定leaseTime,不启用看门狗
lock.lock(10, TimeUnit.SECONDS); // 10秒后自动释放,无看门狗

看门狗工作流程:

1、加锁成功后,启动一个后台线程(看门狗)

2、每隔锁过期时间/3(默认10秒)检查一次

3、如果业务还在执行,则重置锁的过期时间(默认30秒)

4、业务完成后,停止看门狗线程

Lua脚本保证原子性

java 复制代码
// Redisson释放锁的Lua脚本
String script = 
    "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
    "    return nil;" +
    "end; " +
    "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
    "if (counter > 0) then " +
    "    redis.call('pexpire', KEYS[1], ARGV[2]); " +
    "    return 0; " +
    "else " +
    "    redis.call('del', KEYS[1]); " +
    "    redis.call('publish', KEYS[2], ARGV[1]); " +
    "    return 1; " +
    "end; " +
    "return nil;";

脚本解释:

KEYS[1]:锁的key

KEYS[2]:发布订阅的channel

ARGV[1]:解锁消息

ARGV[2]:锁的过期时间

ARGV[3]:客户端标识(UUID:threadId)

功能:验证持有者 → 重入计数减1 → 判断是否完全释放 → 删除锁并通知等待者

4. 生产环境最佳实践

java 复制代码
@Component
public class OrderService {
    @Autowired
    private RedissonClient redisson;
    
    public void processOrder(String orderId) {
        String lockKey = "order:lock:" + orderId;
        RLock lock = redisson.getLock(lockKey);
        
        // 尝试获取锁,最多等待5秒,锁持有时间10秒
        try {
            boolean acquired = lock.tryLock(5, 10, TimeUnit.SECONDS);
            if (!acquired) {
                throw new BusinessException("系统繁忙,请稍后重试");
            }
            
            // 获取锁成功,执行业务逻辑
            handleOrder(orderId);
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new BusinessException("处理被中断");
        } finally {
            // 只有当前线程持有锁时才释放
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
    
    // 异步处理,避免长时间占用锁
    @Async
    public CompletableFuture<Void> asyncProcessOrder(String orderId) {
        return CompletableFuture.runAsync(() -> processOrder(orderId));
    }
}
  1. 性能调优与监控
yaml 复制代码
Redisson配置示例
redisson:
  single-server-config:
    address: "redis://localhost:6379"
    connection-pool-size: 100          # 连接池大小
    connection-minimum-idle-size: 10   # 最小空闲连接数
    idle-connection-timeout: 10000     # 空闲连接超时时间
    connect-timeout: 10000             # 连接超时时间
    timeout: 3000                      # 操作超时时间
    retry-attempts: 3                  # 重试次数
    retry-interval: 1500               # 重试间隔
    
  lock:
    watchdog-timeout: 30000            # 看门狗超时时间
    lock-watch-timeout: 10000          # 锁监控超时

6. 常见问题与解决方案

总结

Redis分布式锁从基础到生产级的演进,体现了对可靠性、性能、可用性的不断追求:

1、基础版:简单但危险,仅适用于学习和测试

2、原子SET版:满足基本需求,但需自行处理续期和释放

3、Redisson版:生产级方案,提供可重入、公平锁、联锁、红锁等高级特性,内置看门狗和原子性保证

选择建议:

1、大部分场景:使用Redisson普通锁(可重入锁)

2、需要严格顺序:公平锁

3、多资源原子操作:联锁

4、极高可用性需求:红锁(但需权衡复杂性)

黄金法则:永远在finally块中释放锁,并验证当前线程是否持有锁。

相关推荐
qq_12498707531 小时前
基于SpringCloud的分布式演唱会抢票系统(源码+论文+部署+安装)
分布式·spring·spring cloud·毕业设计·计算机毕业设计
vx-bot5556661 小时前
1024proxy现代对抗性环境下的分布式流量调度系统架构设计
分布式·系统架构
2501_941805938 小时前
在大阪智能零售场景中构建支付实时处理与高并发顾客行为分析平台的工程设计实践经验分享
数据库
李慕婉学姐8 小时前
【开题答辩过程】以《基于JAVA的校园即时配送系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·开发语言·数据库
珠海西格电力8 小时前
零碳园区有哪些政策支持?
大数据·数据库·人工智能·物联网·能源
数据大魔方9 小时前
【期货量化实战】日内动量策略:顺势而为的短线交易法(Python源码)
开发语言·数据库·python·mysql·算法·github·程序员创富
Chasing Aurora9 小时前
数据库连接+查询优化
数据库·sql·mysql·prompt·约束
倔强的石头_9 小时前
【金仓数据库】ksql 指南(六)—— 创建与管理用户和权限(KingbaseES 安全控制核心)
数据库
奋进的芋圆10 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
小熊officer10 小时前
Python字符串
开发语言·数据库·python