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)
-
测试步骤:
- 初始化库存:
POST http://localhost:8080/stock/init?productId=1001&initialStock=100
- 用 JMeter 创建 1000 个线程并发请求扣减接口
- 分别测试无锁版本和 Redisson 锁版本,观察最终库存
- 初始化库存:
-
测试结果:
- 无锁版本:最终库存可能为负数(如 -23),出现超卖
- Redisson 锁版本:最终库存为 0,无超卖,并发安全
五、Redisson 锁高级特性解析
5.1 自动续期(看门狗机制)
当调用 lock(30, TimeUnit.SECONDS)
时,Redisson 会启动一个后台线程(看门狗),每隔 10 秒(30/3)检查锁是否仍被当前线程持有:
- 若持有,则延长锁的过期时间至 30 秒
- 若业务执行完成(调用
unlock()
),则停止续期 - 若线程意外终止,看门狗也会终止,锁会在 30 秒后自动释放
优势:无需预估业务执行时间,避免锁提前释放导致的并发问题。
5.2 红锁(RedLock)机制
针对 Redis 主从集群的"脑裂"问题(主节点宕机,从节点未同步锁信息),Redisson 实现了红锁算法:
- 同时向多个独立的 Redis 节点(通常 5 个)请求加锁
- 只有超过半数(≥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();}}}
}
六、最佳实践与注意事项
-
锁的粒度要细:
- 错误示例:用一个全局锁
stock:lock
控制所有商品的库存 - 正确示例:按商品ID加锁
stock:lock:1001
,不同商品的锁互不影响,提高并发效率
- 错误示例:用一个全局锁
-
避免长时间持有锁:
- 锁内逻辑应尽量简短,避免 IO 密集型操作(如数据库查询、远程调用)
- 若必须执行耗时操作,可考虑异步处理,缩短锁持有时间
-
释放锁的正确姿势:
- 必须在
finally
中释放锁,确保业务异常时也能释放 - 释放前判断
lock.isHeldByCurrentThread()
,避免释放其他线程的锁
- 必须在
-
集群环境推荐红锁:
- 单节点 Redis 存在单点故障风险,生产环境建议用 Redis 集群 + 红锁机制
-
监控与告警:
- 监控锁的获取成功率、等待时间、续期次数
- 当锁等待时间过长时触发告警,可能预示并发过高或业务逻辑耗时过长
七、总结
Redisson 凭借强大的功能和易用性,成为 Spring Boot 项目实现分布式锁的首选框架。本文从环境搭建、核心用法到实战场景,详细介绍了 Redisson 分布式锁的使用,重点包括:
- 可重入锁解决基本并发问题
- 公平锁保证请求顺序
- 读写锁优化"多读少写"场景
- 红锁机制保证集群环境下的可靠性
掌握 Redisson 分布式锁,能有效解决分布式系统中的超卖、重复提交、资源竞争等问题,为高并发业务保驾护航。
如果本文对你有帮助,欢迎点赞收藏,如有疑问或补充,欢迎在评论区交流!