当前位置: 首页 > news >正文

Spring Boot 整合 Redisson 实现分布式锁:实战指南

Spring Boot 整合 Redisson 实现分布式锁:实战指南

在分布式系统中,分布式锁是解决并发问题的关键组件。Redisson 作为 Redis 官方推荐的 Java 客户端,提供了强大的分布式锁实现,支持自动续期、可重入性、公平锁等高级特性。本文将专注于如何使用 Redisson 框架在 Spring Boot 项目中实现分布式锁,从环境搭建到实战应用,一步到位。

一、Redisson 简介与优势

Redisson 是一个基于 Redis 的 Java 分布式框架,不仅提供了 Redis 客户端功能,更封装了一系列分布式工具类,其中分布式锁是其最常用的功能之一。

Redisson 分布式锁的核心优势

  • 自动续期:内置"看门狗"机制,当业务未执行完时自动延长锁的过期时间,避免锁提前释放
  • 可重入性:支持同一线程多次获取锁,避免死锁
  • 集群支持:提供红锁(RedLock)机制,解决 Redis 集群下的"脑裂"问题
  • 丰富的锁类型:除了普通锁,还支持公平锁、读写锁、联锁等
  • 高性能:基于 Netty 实现,非阻塞 IO,性能优于传统 Jedis 客户端

二、环境准备

2.1 版本兼容说明

Redisson 版本需要与 Redis 版本兼容,推荐组合:

  • Redis 6.x + Redisson 3.16.x 及以上
  • Redis 5.x + Redisson 3.14.x 及以上

本文使用:Spring Boot 2.7.10 + Redisson 3.23.3 + Redis 6.2.6

2.2 引入依赖

pom.xml 中添加 Redisson 依赖(Spring Boot 专用 starter):

<!-- Redisson Spring Boot Starter -->
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.23.3</version>
</dependency><!-- Spring Boot Web(用于测试接口) -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><!-- Lombok(简化代码) -->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional>
</dependency>

2.3 配置 Redisson

Redisson 支持多种配置方式(YAML/JSON/Java 代码),推荐使用 YAML 配置,在 application.yml 中添加:

spring:application:name: redisson-lock-demo# Redisson 配置
redisson:# 超时配置connect-timeout: 3000  # 连接超时时间(毫秒)timeout: 3000          # 命令等待超时时间(毫秒)# 单节点配置(生产环境常用,简单部署)single-server-config:address: redis://localhost:6379  # Redis 地址password: 123456                 # Redis 密码(无密码可省略)database: 0                      # 数据库索引connection-pool-size: 16         # 连接池大小idle-connection-timeout: 30000   # 空闲连接超时时间(毫秒)ping-interval: 0                 # 心跳检测间隔(0表示不检测)# 集群配置(多节点高可用,按需开启)# cluster-servers-config:#   node-addresses:#     - redis://192.168.1.101:6379#     - redis://192.168.1.102:6379#     - redis://192.168.1.103:6379#   scan-interval: 2000  # 集群节点扫描间隔(毫秒)

配置说明

  • 单节点配置适用于开发环境或简单生产环境
  • 集群配置适用于高可用场景,需指定所有节点地址
  • connection-pool-size 建议根据并发量调整(默认 64,过小可能导致连接不足)

三、Redisson 分布式锁核心用法

Redisson 提供了 RLock 接口作为分布式锁的核心 API,支持多种锁操作。以下是最常用的锁类型及用法:

3.1 可重入锁(Reentrant Lock)

最常用的锁类型,支持同一线程多次获取锁,避免自己锁死自己。

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;@Service
public class RedissonLockService {// 注入 Redisson 客户端@Resourceprivate RedissonClient redissonClient;// 锁的 key(按业务场景定义,如商品ID、订单ID)private static final String LOCK_KEY = "product:lock:%s";  // %s 用于替换业务ID/*** 获取锁(阻塞式)* @param businessId 业务ID(如商品ID)* @return 锁对象*/public RLock getLock(String businessId) {String lockKey = String.format(LOCK_KEY, businessId);return redissonClient.getLock(lockKey);}/*** 加锁并执行业务(自动续期)* @param businessId 业务ID* @param task 业务任务*/public void executeWithLock(String businessId, Runnable task) {RLock lock = getLock(businessId);try {// 加锁(30秒过期,Redisson 会自动续期)lock.lock(30, TimeUnit.SECONDS);// 执行业务task.run();} finally {// 释放锁(仅当前线程持有锁时才释放)if (lock.isHeldByCurrentThread()) {lock.unlock();}}}/*** 尝试加锁(非阻塞式)* @param businessId 业务ID* @param waitTime 最多等待时间* @param leaseTime 锁持有时间* @param task 业务任务* @return 是否加锁成功并执行*/public boolean tryExecuteWithLock(String businessId, long waitTime, long leaseTime, TimeUnit unit, Runnable task) {RLock lock = getLock(businessId);try {// 尝试加锁:最多等 waitTime,持有 leaseTime 后自动释放boolean locked = lock.tryLock(waitTime, leaseTime, unit);if (locked) {task.run();return true;}return false;} catch (InterruptedException e) {Thread.currentThread().interrupt();return false;} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}}}
}

3.2 公平锁(Fair Lock)

公平锁保证线程获取锁的顺序与请求顺序一致,避免"饥饿"现象(某些线程长期获取不到锁)。适用于对顺序性要求高的场景。

/*** 获取公平锁*/
public RLock getFairLock(String businessId) {String lockKey = String.format(LOCK_KEY, businessId);return redissonClient.getFairLock(lockKey);
}// 使用方式与可重入锁一致
public void executeWithFairLock(String businessId, Runnable task) {RLock fairLock = getFairLock(businessId);try {fairLock.lock(30, TimeUnit.SECONDS);task.run();} finally {if (fairLock.isHeldByCurrentThread()) {fairLock.unlock();}}
}

3.3 读写锁(ReadWrite Lock)

适用于"多读少写"场景,允许多个读操作并发执行,但写操作需要独占锁,提高读操作的并发效率。

import org.redisson.api.RReadWriteLock;/*** 读写锁示例:查询用读锁,更新用写锁*/
public class ReadWriteLockService {@Resourceprivate RedissonClient redissonClient;private static final String RW_LOCK_KEY = "product:rwlock:%s";/*** 读操作(共享锁,可并发)*/public void readData(String businessId, Runnable readTask) {RReadWriteLock rwLock = redissonClient.getReadWriteLock(String.format(RW_LOCK_KEY, businessId));RLock readLock = rwLock.readLock();try {readLock.lock(30, TimeUnit.SECONDS);readTask.run();} finally {if (readLock.isHeldByCurrentThread()) {readLock.unlock();}}}/*** 写操作(独占锁,互斥)*/public void writeData(String businessId, Runnable writeTask) {RReadWriteLock rwLock = redissonClient.getReadWriteLock(String.format(RW_LOCK_KEY, businessId));RLock writeLock = rwLock.writeLock();try {writeLock.lock(30, TimeUnit.SECONDS);writeTask.run();} finally {if (writeLock.isHeldByCurrentThread()) {writeLock.unlock();}}}
}

四、实战场景:分布式库存扣减

以电商库存扣减为例,演示 Redisson 锁如何解决超卖问题。

4.1 库存服务实现

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;@Service
@Slf4j
public class StockService {@Resourceprivate StringRedisTemplate stringRedisTemplate;  // Spring 提供的 Redis 操作工具@Resourceprivate RedissonLockService redissonLockService;  // 上文实现的锁服务// 库存 Redis Key(格式:stock:{商品ID})private static final String STOCK_KEY = "stock:%s";/*** 初始化库存*/public void initStock(String productId, int initialStock) {stringRedisTemplate.opsForValue().set(String.format(STOCK_KEY, productId), String.valueOf(initialStock));log.info("初始化库存成功,商品ID:{},初始库存:{}", productId, initialStock);}/*** 扣减库存(无锁版本,高并发下会超卖)*/public boolean deductStockWithoutLock(String productId) {String stockKey = String.format(STOCK_KEY, productId);Integer stock = Integer.valueOf(stringRedisTemplate.opsForValue().get(stockKey));if (stock == null || stock <= 0) {log.error("库存不足,商品ID:{}", productId);return false;}// 高并发下此处会超卖(非原子操作)stringRedisTemplate.opsForValue().set(stockKey, String.valueOf(stock - 1));log.info("扣减库存成功,商品ID:{},剩余库存:{}", productId, stock - 1);return true;}/*** 扣减库存(Redisson 锁版本,避免超卖)*/public boolean deductStockWithLock(String productId) {// 使用 Redisson 锁执行扣减逻辑final boolean[] result = {false};redissonLockService.executeWithLock(productId, () -> {String stockKey = String.format(STOCK_KEY, productId);Integer stock = Integer.valueOf(stringRedisTemplate.opsForValue().get(stockKey));if (stock != null && stock > 0) {stringRedisTemplate.opsForValue().set(stockKey, String.valueOf(stock - 1));log.info("扣减库存成功,商品ID:{},剩余库存:{}", productId, stock - 1);result[0] = true;} else {log.error("库存不足,商品ID:{}", productId);result[0] = false;}});return result[0];}/*** 查询库存*/public Integer getStock(String productId) {String stock = stringRedisTemplate.opsForValue().get(String.format(STOCK_KEY, productId));return stock == null ? 0 : Integer.parseInt(stock);}
}

4.2 接口暴露(用于测试)

import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;@RestController
@RequestMapping("/stock")
public class StockController {@Resourceprivate StockService stockService;/*** 初始化库存*/@PostMapping("/init")public String initStock(@RequestParam String productId, @RequestParam int initialStock) {stockService.initStock(productId, initialStock);return "初始化成功,商品ID:" + productId + ",库存:" + initialStock;}/*** 无锁扣减库存(测试超卖用)*/@PostMapping("/deduct/no-lock")public String deductWithoutLock(@RequestParam String productId) {boolean success = stockService.deductStockWithoutLock(productId);return success ? "扣减成功" : "库存不足";}/*** Redisson 锁扣减库存*/@PostMapping("/deduct/redisson-lock")public String deductWithLock(@RequestParam String productId) {boolean success = stockService.deductStockWithLock(productId);return success ? "扣减成功" : "库存不足";}/*** 查询库存*/@GetMapping("/query")public String queryStock(@RequestParam String productId) {return "商品ID:" + productId + ",当前库存:" + stockService.getStock(productId);}
}

4.3 高并发测试(JMeter)

  1. 测试步骤

    • 初始化库存:POST http://localhost:8080/stock/init?productId=1001&initialStock=100
    • 用 JMeter 创建 1000 个线程并发请求扣减接口
    • 分别测试无锁版本和 Redisson 锁版本,观察最终库存
  2. 测试结果

    • 无锁版本:最终库存可能为负数(如 -23),出现超卖
    • Redisson 锁版本:最终库存为 0,无超卖,并发安全

五、Redisson 锁高级特性解析

5.1 自动续期(看门狗机制)

当调用 lock(30, TimeUnit.SECONDS) 时,Redisson 会启动一个后台线程(看门狗),每隔 10 秒(30/3)检查锁是否仍被当前线程持有:

  • 若持有,则延长锁的过期时间至 30 秒
  • 若业务执行完成(调用 unlock()),则停止续期
  • 若线程意外终止,看门狗也会终止,锁会在 30 秒后自动释放

优势:无需预估业务执行时间,避免锁提前释放导致的并发问题。

5.2 红锁(RedLock)机制

针对 Redis 主从集群的"脑裂"问题(主节点宕机,从节点未同步锁信息),Redisson 实现了红锁算法:

  1. 同时向多个独立的 Redis 节点(通常 5 个)请求加锁
  2. 只有超过半数(≥3)节点加锁成功,才认为总锁加锁成功
  3. 释放锁时,向所有节点发送释放请求

使用方式

import org.redisson.api.RedissonClient;
import org.redisson.client.RedisClient;import java.util.Arrays;
import java.util.List;public class RedLockService {@Resourceprivate RedissonClient redissonClient;public void executeWithRedLock(String businessId, Runnable task) {// 多个独立 Redis 节点的锁List<String> nodeAddresses = Arrays.asList("redis://192.168.1.101:6379","redis://192.168.1.102:6379","redis://192.168.1.103:6379","redis://192.168.1.104:6379","redis://192.168.1.105:6379");// 创建红锁RLock[] locks = nodeAddresses.stream().map(address -> {// 连接每个节点并获取锁RedisClient client = RedisClient.create(address);return client.getLock("product:lock:" + businessId);}).toArray(RLock[]::new);RLock redLock = redissonClient.getRedLock(locks);try {// 红锁加锁(等待100ms,持有30秒)boolean locked = redLock.tryLock(100, 30000, TimeUnit.MILLISECONDS);if (locked) {task.run();}} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {if (redLock.isHeldByCurrentThread()) {redLock.unlock();}}}
}

六、最佳实践与注意事项

  1. 锁的粒度要细

    • 错误示例:用一个全局锁 stock:lock 控制所有商品的库存
    • 正确示例:按商品ID加锁 stock:lock:1001,不同商品的锁互不影响,提高并发效率
  2. 避免长时间持有锁

    • 锁内逻辑应尽量简短,避免 IO 密集型操作(如数据库查询、远程调用)
    • 若必须执行耗时操作,可考虑异步处理,缩短锁持有时间
  3. 释放锁的正确姿势

    • 必须在 finally 中释放锁,确保业务异常时也能释放
    • 释放前判断 lock.isHeldByCurrentThread(),避免释放其他线程的锁
  4. 集群环境推荐红锁

    • 单节点 Redis 存在单点故障风险,生产环境建议用 Redis 集群 + 红锁机制
  5. 监控与告警

    • 监控锁的获取成功率、等待时间、续期次数
    • 当锁等待时间过长时触发告警,可能预示并发过高或业务逻辑耗时过长

七、总结

Redisson 凭借强大的功能和易用性,成为 Spring Boot 项目实现分布式锁的首选框架。本文从环境搭建、核心用法到实战场景,详细介绍了 Redisson 分布式锁的使用,重点包括:

  • 可重入锁解决基本并发问题
  • 公平锁保证请求顺序
  • 读写锁优化"多读少写"场景
  • 红锁机制保证集群环境下的可靠性

掌握 Redisson 分布式锁,能有效解决分布式系统中的超卖、重复提交、资源竞争等问题,为高并发业务保驾护航。

如果本文对你有帮助,欢迎点赞收藏,如有疑问或补充,欢迎在评论区交流!

http://www.dtcms.com/a/426681.html

相关文章:

  • 国鑫发布新一代「海擎」服务器 全面兼容国内外主流OAM GPU
  • 百度电商MultiAgent视频生成系统
  • FRP v0.65.0 内网穿透专业指南(SSH + HTTP/HTTPS 一体化配置)
  • UNIX下C语言编程与实践20-UNIX 文件类型判断:stat 结构 st_mode 与文件类型宏的使用实战
  • 电脑网站开发手机上可以打开吗网站建设如何把代码
  • ROS2下利用遥控手柄控制瑞尔曼RM65-B机器人
  • SOC(安全运营中心)
  • 济南网站建设山东聚搜网推荐传媒公司招聘
  • C++ STL 深度解析:容器、迭代器与算法的协同作战
  • SPI主控的CS引发的读不到设备寄存器
  • 数据标注、Label Studio
  • 央链知播受权发布:图说《“可信资产 IPO + 数链金融 RWA” 链改 2.0 六方共识》
  • 【Proteus8.17仿真】 STM32仿真 0.96OLED 屏幕显示ds1302实时时间
  • 佛山做营销型网站建设wordpress修改域名后无法登陆
  • mysql数据库学习之常用函数(五)
  • 避坑实战!京东商品详情接口开发指南:分页优化、多规格解析与数据完整性保障
  • win10(十二)Nuitka打包程序
  • 【Rust GUI开发入门】编写一个本地音乐播放器(11. 支持动态明暗主题切换)
  • 自己做网站帮公司出认证证书违法吗上海定制网站建设公司
  • [论文阅读] AI + 软件工程(Debug)| 告别 “猜 bug”:TreeMind 用 LLM+MCTS 破解 Android 不完整报告复现难题
  • ESP32 + MCP over MQTT:通过大模型控制智能硬件设备
  • 五大关系数据库(sqlserver、mysql、oracle、pgsql、sqlite)的对象名称和转义字符
  • 央企云原生PaaS建设方案及案例集锦
  • 使用Django从零开始构建一个个人博客系统
  • 工业互联网的云原生转型路径
  • 做网站需要哪些素材建筑工程 技术支持 东莞网站建设
  • Spring Boot 缓存技术
  • AI应用生成平台:数据库、缓存与存储
  • J2Cache 多级缓存配置与使用
  • 【JAVA】【BUG】经常出现的典型 bug 及解决办法