
作为互联网软件开发同行,你是不是也遇到过这样的情况:在 Spring Boot3 微服务项目里,多实例部署后,库存扣减出现超卖、订单重复生成,排查半天发现是并发场景下的资源竞争问题?明明在单服务里用了本地锁,到了分布式环境下却完全失效 —— 这实则就是没做好分布式锁导致的。今天咱们就聚焦一个核心问题:Spring Boot3 到底该怎么用 Redisson 实现可靠的分布式锁? 从问题根源到实操步骤,咱们一步步说清楚。
为啥分布式环境下,本地锁不管用了?
在单服务部署时,我们用 synchronized 或者 ReentrantLock 就能解决并发问题,由于所有请求都在同一个 JVM 进程里,锁能控制所有线程的资源访问。但到了微服务架构,一个服务部署 3 台、5 台实例是常事,列如用户下单请求,可能被负载均衡分发到实例 A、实例 B、实例 C 上 —— 这时候本地锁就像 “家门钥匙只锁了自己家,却管不了邻居家的门”,实例 A 的锁挡不住实例 B 的线程访问一样资源,最终必然出现数据错乱。
那为啥选 Redisson 做分布式锁?市面上常见的分布式锁方案里,Redis 原生锁需要自己处理过期时间、死锁预防,代码繁琐还容易出 bug;ZooKeeper 锁性能又稍弱,不适合高并发场景。而 Redisson 作为 Redis 的 Java 客户端,不仅封装了分布式锁的底层逻辑(列如自动续期、可重入、公平锁支持),还能和 Spring Boot3 无缝集成,咱们开发时不用重复造轮子,效率和可靠性都能兼顾。
实操步骤:Spring Boot3 集成 Redisson 分布式锁,4 步就能搞定
第一步:引入依赖,少走版本兼容的坑
Spring Boot3 对应的 Redisson 版本有讲究,提议用 3.20.x 及以上版本,避免和 Spring Boot3 的依赖冲突。在 pom.xml 里添加这两个依赖:
<!-- Redisson核心依赖 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.21.3</version>
</dependency>
<!-- Spring Boot3 Web依赖(如果项目已有可忽略) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
这里提醒一句:如果你的项目用的是 Gradle,依赖配置可以对应调整,核心是保证 Redisson starter 版本和 Spring Boot3 匹配,实在不确定的话,去 Redisson 官网查最新的兼容说明。
第二步:配置 Redis 连接,3 行配置搞定基础信息
在 application.yml(或 application.properties)里添加 Redis 的连接信息,Redisson 会自动读取这些配置创建客户端:
spring:
redis:
host: 127.0.0.1 # 你的Redis地址
port: 6379 # Redis端口
password: 123456 # Redis密码(没有的话可省略)
database: 0 # 使用的Redis数据库索引
如果是 Redis 集群或者哨兵模式,配置稍复杂一点,但 Redisson 也支持,列如集群配置只需加 “cluster.nodes” 字段,具体可以参考 Redisson 的官方文档,这里咱们先以单机 Redis 为例,聚焦锁的核心用法。
第三步:核心代码实现,两种锁场景要分清
Redisson 支持多种锁类型,咱们开发中最常用的是可重入锁(ReentrantLock)和公平锁(FairLock),先看最基础的可重入锁实现:
第一创建一个 Service 层接口,列如 InventoryService,模拟库存扣减场景:
public interface InventoryService {
// 扣减库存方法,参数:商品ID、扣减数量
boolean deductInventory(Long productId, Integer quantity);
}
然后写实现类,注入 RedissonClient,用 lock () 和 unlock () 控制锁的获取与释放:
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 InventoryServiceImpl implements InventoryService {
// 注入Redisson客户端(自动配置,无需手动创建)
@Resource
private RedissonClient redissonClient;
// 模拟库存存储,实际项目中应该用数据库
private static final HashMap<Long, Integer> INVENTORY_MAP = new HashMap<>();
// 初始化库存,列如商品ID=1001的库存有100件
static {
INVENTORY_MAP.put(1001L, 100);
}
@Override
public boolean deductInventory(Long productId, Integer quantity) {
// 1.定义锁的key:用商品ID区分不同锁,避免锁冲突
String lockKey = "inventory:lock:" + productId;
// 2.获取可重入锁
RLock lock = redissonClient.getLock(lockKey);
try {
// 3.获取锁:最多等待5秒,获取到锁后10秒自动释放(防止死锁)
boolean isLocked = lock.tryLock(5, 10, TimeUnit.SECONDS);
if (!isLocked) {
// 没获取到锁,返回失败(列如提示“当前请求过多,请稍后再试”)
System.out.println("获取锁失败,商品ID:" + productId);
return false;
}
// 4.获取到锁,执行库存扣减逻辑
Integer currentInventory = INVENTORY_MAP.get(productId);
if (currentInventory == null || currentInventory < quantity) {
System.out.println("库存不足,商品ID:" + productId + ",当前库存:" + currentInventory);
return false;
}
// 扣减库存
INVENTORY_MAP.put(productId, currentInventory - quantity);
System.out.println("库存扣减成功,商品ID:" + productId + ",剩余库存:" + (currentInventory - quantity));
return true;
} catch (InterruptedException e) {
// 处理中断异常
Thread.currentThread().interrupt();
System.out.println("扣减库存时发生中断,商品ID:" + productId);
return false;
} finally {
// 5.释放锁:只有当前线程持有锁时才释放,避免误释放别人的锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
这里有两个关键细节要注意:
一是锁的 key 必须 “唯一”,列如用 “业务类型 + 商品 ID” 作为 key,避免不同业务、不同商品共用一把锁,导致 “锁粒度太大” 的问题;
二是 tryLock () 的三个参数:等待时间(5 秒)、自动释放时间(10 秒)、时间单位,这三个参数能有效预防死锁 —— 即使服务突然宕机,10 秒后锁也会自动释放,不会卡住资源。
如果你的业务需要 “公平锁”(即先请求的线程先获取锁,避免线程饥饿),只需把获取锁的代码改成:
// 获取公平锁
RLock fairLock = redissonClient.getFairLock(lockKey);
// 后续的tryLock()和释放锁逻辑和可重入锁一致
公平锁适合对 “顺序性” 要求高的场景,列如秒杀活动中的排队逻辑,但要注意:公平锁的性能比可重入锁稍低,非必要场景优先用可重入锁。
第四步:测试验证,模拟高并发场景
为了验证分布式锁的效果,咱们写一个 Controller 层接口,用 Postman 或者 JMeter 模拟多请求并发:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/inventory")
public class InventoryController {
@Resource
private InventoryService inventoryService;
// 扣减库存接口,参数:商品ID、扣减数量
@GetMapping("/deduct")
public String deduct(@RequestParam Long productId, @RequestParam Integer quantity) {
boolean result = inventoryService.deductInventory(productId, quantity);
return result ? "库存扣减成功" : "库存扣减失败(库存不足或请求过多)";
}
}
启动 Spring Boot3 项目后,用 JMeter 创建 100 个线程,同时调用
http://localhost:8080/inventory/deduct?productId=1001&quantity=1—— 如果没有分布式锁,100 次请求后库存可能会变成负数(超卖);而加上 Redisson 锁后,最终库存会准确变成 0,不会出现超卖问题,这就说明锁生效了。
三、避坑指南:这 3 个细节不注意,锁等于白加
不要忘记释放锁,必须放在 finally 里:如果业务逻辑执行中抛出异常,没释放的锁会一直占用,直到自动过期,这期间会影响其他请求。所以必定要把 unlock () 放在 finally 块里,确保无论是否有异常,锁都会被释放。
避免 “锁超时” 导致业务没执行完:如果你的业务逻辑执行时间超过了 tryLock () 设置的 “自动释放时间”(列如 10 秒),锁会提前释放,导致并发问题。这时候可以用 Redisson 的 “自动续期” 功能 —— 不用手动设置自动释放时间,锁会在业务执行期间自动续期,业务结束后再释放,代码只需把 tryLock () 改成:
// 只设置等待时间,不设置自动释放时间,Redisson会自动续期
boolean isLocked = lock.tryLock(5, TimeUnit.SECONDS);
锁的粒度不能太粗也不能太细:列如给 “所有商品的库存” 用一把锁(key=“inventory:lock”),会导致所有商品的扣减请求都排队,性能太差;但如果给 “每个库存变更操作” 都用不同的锁,又会导致锁太多,管理复杂。提议按 “业务 + 唯一标识”(如商品 ID、用户 ID)设计锁 key,平衡性能和可靠性。
总结:Redisson 分布式锁,记住这 3 个核心点
- 集成简单:Spring Boot3+Redisson starter,依赖 + 配置两步搞定,不用手动管理 Redis 连接和锁的底层逻辑;
- 用法灵活:支持可重入锁、公平锁、读写锁等多种锁类型,满足不同业务场景;
- 可靠性高:自动续期、死锁预防、线程安全释放,解决了 Redis 原生锁的诸多痛点。
最后想跟同行们说:分布式锁看似简单,但实际项目中 “超卖”“重复下单” 这些问题,许多时候都是锁的细节没处理好导致的。如果你在 Spring Boot3 集成 Redisson 的过程中,遇到了版本兼容、锁失效、性能瓶颈等问题,欢迎在评论区留言分享 —— 咱们一起讨论解决方案,少踩坑、多提效!





