Java Redisson锁案例如何使用

wen java案例 28

Java Redisson分布式锁实战案例与避坑指南

📑 目录导读

  1. 为什么需要分布式锁?Redisson锁的核心价值
  2. Redisson锁的8种常见模式与配置
  3. 实战案例:秒杀系统、重复提交、定时任务
  4. 必须避开的5大雷区
  5. 高频问答Q&A(含代码演示)

引言:单机锁的“天花板”与分布式锁的诞生

在单体架构中,Java开发者通过synchronizedReentrantLock即可轻松解决并发问题,但当系统拆分为微服务、部署在多台服务器上时,两个线程可能分别运行在不同的JVM中,此时单机锁便彻底失效——这便是分布式锁的用武之地。

Java Redisson锁案例如何使用

Redisson锁(基于Redis的Java客户端)提供了可重入、高可用、支持Watchdog自动续期的分布式锁,已成为业界标准方案,本文将结合真实案例,剖析其核心用法。


第一部分:Redisson锁的8种模式与配置

1 基础配置(Spring Boot整合)

@Bean
public RedissonClient redissonClient() {
    Config config = new Config();
    config.useSingleServer().setAddress("redis://127.0.0.1:6379");
    return Redisson.create(config);
}

2 锁模式速查表

模式 方法名 适用场景
可重入锁 getLock() 业务方法内部递归调用
公平锁 getFairLock() 需要排队顺序执行业务
联锁 getMultiLock() 多个Redis节点同时加锁
红锁 getRedLock() 高一致性要求场景
读写锁 getReadWriteLock() 读多写少场景
信号量 getSemaphore() 限流控制
闭锁 getCountDownLatch() 等待N个条件完成
自旋锁 tryLock(5, 3, TimeUnit.SECONDS) 快速失败场景

第二部分:实战案例(代码级解析)

秒杀系统库存扣减(最经典)

RLock lock = redissonClient.getLock("seckill:stock:1001");
try {
    // 尝试加锁,等待3秒,持有锁30秒(Watchdog自动续期)
    if (lock.tryLock(3, 30, TimeUnit.SECONDS)) {
        int stock = getStock(1001);
        if (stock > 0) {
            deductStock(1001);
            return "抢购成功";
        } else {
            return "库存不足";
        }
    } else {
        return "系统繁忙,请稍后重试";
    }
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    return "系统异常";
} finally {
    if (lock.isHeldByCurrentThread()) {
        lock.unlock();
    }
}

作用:防止高并发下超卖,Watchdog会在业务执行期间自动续期,避免锁提前释放。

防止接口重复提交

// 用户id + 业务id作为锁标识
String lockKey = "submit:order:" + userId + ":" + orderId;
RLock lock = redissonClient.getLock(lockKey);
boolean acquired = lock.tryLock(0, 10, TimeUnit.SECONDS);
if (!acquired) {
    throw new BusinessException("请勿重复提交");
}
try {
    processOrder();
} finally {
    lock.unlock();
}

技巧tryLock(0,...)表示立即尝试,不等待,适合对响应时间敏感的接口。

分布式定时任务分布式协调

@Scheduled(cron = "0 0 2 * * ?")
public void runDailyTask() {
    RLock lock = redissonClient.getLock("cron:data-sync");
    if (lock.tryLock(0, 30, TimeUnit.MINUTES)) {
        try {
            // 执行业务,如同步数据
        } finally {
            lock.unlock();
        }
    }
}

注意:单机定时任务+分布式锁=集群中只有一个任务执行,避免重复触发。


第三部分:必须避开的5大雷区

❌ 雷区1:忘记解锁导致死锁

lock.lock();
// 业务抛异常 -> 直接退出 -> 未解锁

解法:必须放在finally块中,或使用tryLock并捕获异常。

❌ 雷区2:锁粒度太粗

// 错误:锁定整个商品类别
lock("category:update");
// 正确:只锁定单个商品
lock("product:update:" + productId);

❌ 雷区3:Watchdog失效(高版本Redisson需配置)

redisson:
  lock-watchdog-timeout: 30000  # 默认30秒

原理:Watchdog每10秒检查一次,如果业务未结束则续期,若业务耗时>看门狗续期间隔,需手动调整。

❌ 雷区4:Redis集群脑裂导致锁丢失

解法:使用RedLock(红锁),但性能会下降30%~50%。

❌ 雷区5:锁超时时间设置过短

// 业务可能执行2分钟,但锁超时设了10秒 -> 锁提前释放

解法:合理估算业务耗时+Watchdog兜底。


第四部分:高频问答Q&A(附代码)

Q1:Redisson的lock()tryLock()有什么区别?

方法 特点 代码示例
lock() 阻塞等待,直到获取锁 适合需要必须获取锁的场景
tryLock() 尝试获取,可设置等待时间 适合超时放弃的场景

Q2:如何测试分布式锁是否生效?

使用JMeter并发请求秒杀接口,观察日志:

// 在加锁前后打印线程id
log.info("尝试获取锁,线程id: {}", Thread.currentThread().getId());

若不同线程的锁标识冲突,则输出成功。

Q3:锁的key如何设计更合理?

推荐格式: 业务域:资源类型:资源ID:操作

order:create:user-{userId}  // 用户维度
seckill:stock:{productId}   // 商品维度

避免使用IP或动态字符串,防止key冲突。

Q4:锁升级后需要重新加锁吗?

答:不需要RLock是可重入的,同一线程可多次加锁(计数递增),解锁时递减。


用好Redisson锁的三个原则

  1. 锁粒度最小化:越细,并发能力越高
  2. 超时设置合理化:业务耗时+20%余量
  3. 异常处理完整化:try-catch-finally / tryLock自旋

最后建议:在非极端高一致性场景(如点赞、统计),考虑用Redis的SET NX+Lua脚本替代完整锁,以降低延迟;但涉及资金、库存等关键业务,务必使用Redisson的完整锁机制

参考资料:Redisson官方文档、阿里巴巴Java开发手册(分布式锁章节)、Stack Overflow高频问答整理

抱歉,评论功能暂时关闭!