Redisson分布式锁详细教程 - 从入门到实战

发布于:2025-07-09 ⋅ 阅读:(21) ⋅ 点赞:(0)

目录

​​​1. 什么是分布式锁?

生活中的例子

技术层面的理解

2. 为什么需要分布式锁?

单体应用 vs 分布式应用

单体应用(简单)

分布式应用(复杂)

分布式环境的挑战

3. 分布式锁的核心要求

3.1 互斥性(Mutual Exclusion)

3.2 安全性(Safety)

3.3 活性(Liveness)

3.4 高可用(High Availability)

4. Redisson分布式锁基础

4.1 基本概念

4.2 快速开始

4.3 核心API解析

5. 基本分布式锁详解

5.1 完整示例:库存扣减

5.2 并发测试验证

5.3 可重入锁示例

6. 公平锁vs非公平锁

6.1 概念对比

6.2 非公平锁示例

6.3 公平锁示例

6.4 选择建议

7. 读写锁详解

7.1 读写锁的特性

7.2 缓存更新场景

7.3 读写锁测试

7.4 读写锁的性能优势

8. 联锁(多重锁)

8.1 什么是联锁?

8.2 转账业务实现

8.3 避免死锁

9. 红锁(RedLock)

9.1 红锁的背景

9.2 红锁配置

9.3 红锁使用示例

9.4 红锁 vs 普通分布式锁

10. 实际应用场景

10.1 防止重复提交

10.2 定时任务分布式执行

10.3 分布式限流

11. 性能优化技巧

11.1 锁粒度优化

11.2 锁超时设置优化

11.3 连接池优化

11.4 批量锁操作

12. 常见坑点和解决方案

12.1 锁重入问题

12.2 锁释放问题

12.3 超时设置不当

12.4 Redis连接异常处理

13. 监控和调试

13.1 锁监控指标

13.2 锁状态查询

13.3 锁调试工具

总结

🎯 核心要点

📋 最佳实践清单

🚀 进阶建议


​​​1. 什么是分布式锁?

生活中的例子

想象一下这个场景:

🚻 公共厕所的锁

  • 当你进入厕所时,你会把门锁上
  • 其他人看到门锁着,就知道里面有人,会等待
  • 你用完后开锁出来,下一个人才能进入

🏦 银行ATM机

  • 同一时间只能有一个人使用ATM
  • 其他人必须排队等待
  • 前一个人操作完成后,下一个人才能开始

技术层面的理解

// 想象这是你的业务代码
public void transferMoney(String fromAccount, String toAccount, BigDecimal amount) {
    // 步骤1: 检查余额
    BigDecimal balance = getBalance(fromAccount);
    
    // 步骤2: 判断余额是否足够
    if (balance.compareTo(amount) >= 0) {
        // 步骤3: 扣除金额
        deductMoney(fromAccount, amount);
        // 步骤4: 增加金额
        addMoney(toAccount, amount);
    }
}

问题:如果多个请求同时执行这段代码会怎样?

  1. 请求A和请求B同时检查余额,都发现余额充足
  2. 请求A扣除100元
  3. 请求B也扣除100元
  4. 结果:账户被扣了200元,但只转出了100元!

分布式锁的作用:确保同一时间只有一个请求能执行这段代码。


2. 为什么需要分布式锁?

单体应用 vs 分布式应用

单体应用(简单)
// 在单体应用中,我们可以用Java的synchronized
public synchronized void transferMoney(...) {
    // 业务逻辑
}

特点

  • 所有代码在一个JVM中运行
  • synchronized可以保证线程安全
  • 简单有效
分布式应用(复杂)
用户请求 → 负载均衡器 → 服务器A (JVM1)
                    → 服务器B (JVM2)  
                    → 服务器C (JVM3)

问题

  • synchronized只能在单个JVM内生效
  • 不同服务器上的代码无法使用synchronized互斥
  • 需要一个"全局"的锁机制

分布式环境的挑战

// 服务器A上的代码
public class ServerA {
    public synchronized void processOrder(String orderId) {
        // 处理订单
        Order order = getOrder(orderId);
        if (order.getStatus() == PENDING) {
            order.setStatus(PROCESSING);
            updateOrder(order);
            // 实际处理逻辑...
        }
    }
}

// 服务器B上的代码  
public class ServerB {
    public synchronized void processOrder(String orderId) {
        // 同样的处理逻辑
        // 但是和服务器A的synchronized互不影响!
    }
}

结果:同一个订单可能被两台服务器同时处理!


3. 分布式锁的核心要求

一个好的分布式锁必须满足以下特性:

3.1 互斥性(Mutual Exclusion)

// 同一时间只有一个客户端能获得锁
if (客户端A已经获得锁) {
    客户端B无法获得锁; // 必须等待
}

3.2 安全性(Safety)

// 只有获得锁的客户端才能释放锁
if (lockOwner != currentClient) {
    throw new Exception("你不能释放别人的锁!");
}

3.3 活性(Liveness)

// 即使持有锁的客户端崩溃,锁也要能被释放
lockWithTimeout(30, TimeUnit.SECONDS); // 30秒后自动释放

3.4 高可用(High Availability)

// Redis集群模式下,即使部分节点宕机,锁服务仍然可用

4. Redisson分布式锁基础

4.1 基本概念

Redisson分布式锁的底层原理

  1. 使用Redis的SET命令设置一个key
  2. 设置过期时间防止死锁
  3. 使用Lua脚本保证操作原子性
  4. 通过Redis的发布订阅机制实现锁等待

4.2 快速开始

// 1. 创建Redisson客户端
RedissonClient redisson = Redisson.create();

// 2. 获取锁对象
RLock lock = redisson.getLock("myBusinessLock");

// 3. 使用锁
try {
    // 尝试获取锁
    boolean acquired = lock.tryLock(3, 10, TimeUnit.SECONDS);
    //                              ↑    ↑
    //                        等待时间  自动释放时间
    
    if (acquired) {
        // 获取锁成功,执行业务逻辑
        System.out.println("获得锁,开始处理业务");
        doBusinessLogic();
    } else {
        // 获取锁失败
        System.out.println("获取锁失败,请稍后重试");
    }
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
} finally {
    // 释放锁(只有锁的持有者才能释放)
    if (lock.isHeldByCurrentThread()) {
        lock.unlock();
        System.out.println("释放锁成功");
    }
}

4.3 核心API解析

// 获取锁对象
RLock lock = redisson.getLock("lockName");

// 方法1: 阻塞等待获取锁(危险!可能永远等待)
lock.lock();

// 方法2: 带超时的阻塞等待
lock.lock(10, TimeUnit.SECONDS); // 10秒后自动释放

// 方法3: 尝试获取锁(推荐)
boolean success = lock.tryLock(); // 立即返回

// 方法4: 带超时的尝试获取锁(最推荐)
boolean success = lock.tryLock(
    3,  // 等待获取锁的时间
    10, // 锁自动释放时间  
    TimeUnit.SECONDS
);

// 检查锁状态
boolean isLocked = lock.isLocked();           // 锁是否被任何客户端持有
boolean isHeldByMe = lock.isHeldByCurrentThread(); // 锁是否被当前线程持有
int holdCount = lock.getHoldCount();          // 当前线程持有锁的次数(可重入)

// 释放锁
lock.unlock();

// 强制释放锁(谨慎使用)
lock.forceUnlock();

5. 基本分布式锁详解

5.1 完整示例:库存扣减

这是电商系统中最常见的场景:

@Service
public class InventoryService {
    
    @Autowired
    private RedissonClient redisson;
    
    @Autowired
    private InventoryRepository inventoryRepository;
    
    /**
     * 安全的库存扣减
     * @param productId 商品ID
     * @param quantity 扣减数量
     * @return 是否扣减成功
     */
    public boolean deductInventory(Long productId, int quantity) {
        // 为每个商品创建独立的锁
        String lockKey = "inventory_lock:" + productId;
        RLock lock = redisson.getLock(lockKey);
        
        try {
            // 尝试获取锁,等待5秒,锁30秒后自动释放
            boolean acquired = lock.tryLock(5, 30, TimeUnit.SECONDS);
            
            if (!acquired) {
                log.warn("获取库存锁失败,商品ID: {}", productId);
                return false;
            }
            
            log.info("获取库存锁成功,开始处理商品: {}", productId);
            
            // 查询当前库存
            Inventory inventory = inventoryRepository.findByProductId(productId);
            if (inventory == null) {
                log.warn("商品不存在: {}", productId);
                return false;
            }
            
            // 检查库存是否充足
            if (inventory.getQuantity() < quantity) {
                log.warn("库存不足,当前库存: {}, 需要扣减: {}", 
                        inventory.getQuantity(), quantity);
                return false;
            }
            
            // 执行库存扣减
            inventory.setQuantity(inventory.getQuantity() - quantity);
            inventoryRepository.save(inventory);
            
            log.info("库存扣减成功,商品: {}, 扣减数量: {}, 剩余库存: {}", 
                    productId, quantity, inventory.getQuantity());
            
            return true;
            
        } catch (InterruptedException e) {
            log.error("等待锁被中断", e);
            Thread.currentThread().interrupt();
            return false;
        } catch (Exception e) {
            log.error("库存扣减异常", e);
            return false;
        } finally {
            // 确保锁被正确释放
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
                log.info("释放库存锁: {}", productId);
            }
        }
    }
}

5.2 并发测试验证

@Test
public void testConcurrentInventoryDeduction() {
    // 初始化库存
    Long productId = 12345L;
    inventoryRepository.save(new Inventory(productId, 100)); // 初始库存100
    
    // 创建100个并发请求,每个扣减1个库存
    int threadCount = 100;
    CountDownLatch latch = new CountDownLatch(threadCount);
    AtomicInteger successCount = new AtomicInteger(0);
    
    for (int i = 0; i < threadCount; i++) {
        new Thread(() -> {
            try {
                boolean success = inventoryService.deductInventory(productId, 1);
                if (success) {
                    successCount.incrementAndGet();
                }
            } finally {
                latch.countDown();
            }
        }).start();
    }
    
    // 等待所有线程完成
    latch.await();
    
    // 验证结果
    Inventory finalInventory = inventoryRepository.findByProductId(productId);
    System.out.println("成功扣减次数: " + successCount.get());
    System.out.println("最终库存: " + finalInventory.getQuantity());
    System.out.println("预期库存: " + (100 - successCount.get()));
    
    // 断言:最终库存 = 初始库存 - 成功扣减次数
    assertEquals(100 - successCount.get(), finalInventory.getQuantity().intValue());
}

5.3 可重入锁示例

Redisson的锁是可重入的,同一个线程可以多次获取同一把锁:

public class ReentrantLockExample {
    private RedissonClient redisson = Redisson.create();
    private RLock lock = redisson.getLock("reentrantLock");
    
    public void outerMethod() {
        try {
            lock.lock();
            System.out.println("外层方法获得锁,持有次数: " + lock.getHoldCount());
            
            innerMethod(); // 调用内层方法
            
        } finally {
            lock.unlock();
            System.out.println("外层方法释放锁");
        }
    }
    
    public void innerMethod() {
        try {
            lock.lock(); // 同一线程再次获取锁
            System.out.println("内层方法获得锁,持有次数: " + lock.getHoldCount());
            
            // 执行业务逻辑
            System.out.println("执行内层业务逻辑");
            
        } finally {
            lock.unlock();
            System.out.println("内层方法释放锁,剩余持有次数: " + lock.getHoldCount());
        }
    }
}

// 输出结果:
// 外层方法获得锁,持有次数: 1
// 内层方法获得锁,持有次数: 2  
// 执行内层业务逻辑
// 内层方法释放锁,剩余持有次数: 1
// 外层方法释放锁

6. 公平锁vs非公平锁

6.1 概念对比

非公平锁(默认)

  • 新来的线程可能直接获得锁
  • 性能更好
  • 可能导致某些线程长时间等待

公平锁

  • 严格按照申请锁的顺序获得锁
  • 避免饥饿现象
  • 性能相对较差

6.2 非公平锁示例

public class UnfairLockExample {
    public static void main(String[] args) {
        RedissonClient redisson = Redisson.create();
        RLock unfairLock = redisson.getLock("unfairLock"); // 默认非公平锁
        
        // 创建5个线程竞争锁
        for (int i = 0; i < 5; i++) {
            int threadId = i;
            new Thread(() -> {
                for (int j = 0; j < 3; j++) {
                    try {
                        unfairLock.lock();
                        System.out.println("线程" + threadId + "获得锁,第" + (j+1) + "次");
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        unfairLock.unlock();
                    }
                }
            }).start();
        }
    }
}

// 可能的输出(顺序不固定):
// 线程0获得锁,第1次
// 线程0获得锁,第2次  ← 同一线程连续获得锁
// 线程2获得锁,第1次
// 线程1获得锁,第1次
// ...

6.3 公平锁示例

public class FairLockExample {
    public static void main(String[] args) {
        RedissonClient redisson = Redisson.create();
        RLock fairLock = redisson.getFairLock("fairLock"); // 公平锁
        
        // 创建5个线程竞争锁
        for (int i = 0; i < 5; i++) {
            int threadId = i;
            new Thread(() -> {
                for (int j = 0; j < 3; j++) {
                    try {
                        fairLock.lock();
                        System.out.println("线程" + threadId + "获得锁,第" + (j+1) + "次");
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        fairLock.unlock();
                    }
                }
            }).start();
        }
    }
}

// 输出(严格按顺序)&#x

网站公告

今日签到

点亮在社区的每一天
去签到