Spring Boot3 用Redisson实现分布式锁:同行们,这些坑别再踩了!

B站影视 港台电影 2025-10-02 16:39 1

摘要:作为互联网软件开发同行,你是不是也遇到过这样的情况:在 Spring Boot3 微服务项目里,多实例部署后,库存扣减出现超卖、订单重复生成,排查半天发现是并发场景下的资源竞争问题?明明在单服务里用了本地锁,到了分布式环境下却完全失效 —— 这其实就是没做好分

作为互联网软件开发同行,你是不是也遇到过这样的情况:在 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 版本有讲究,建议用 3.20.x 及以上版本,避免和 Spring Boot3 的依赖冲突。在 pom.xml 里添加这两个依赖:

org.redisson redisson-spring-boot-starter 3.21.3 org.springframework.boot spring-boot-starter-web

这里提醒一句:如果你的项目用的是 Gradle,依赖配置可以对应调整,核心是保证 Redisson starter 版本和 Spring Boot3 匹配,实在不确定的话,去 Redisson 官网查最新的兼容说明。

在 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;@Servicepublic class InventoryServiceImpl implements InventoryService { // 注入Redisson客户端(自动配置,无需手动创建) @Resource private RedissonClient redissonClient; // 模拟库存存储,实际项目中应该用数据库 private static final HashMap 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

这里有两个关键细节要注意:

一是锁的 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,不会出现超卖问题,这就说明锁生效了。

不要忘记释放锁,必须放在 finally 里:如果业务逻辑执行中抛出异常,没释放的锁会一直占用,直到自动过期,这期间会影响其他请求。所以一定要把 unlock 放在 finally 块里,确保无论是否有异常,锁都会被释放。

避免 “锁超时” 导致业务没执行完:如果你的业务逻辑执行时间超过了 tryLock 设置的 “自动释放时间”(比如 10 秒),锁会提前释放,导致并发问题。这时候可以用 Redisson 的 “自动续期” 功能 —— 不用手动设置自动释放时间,锁会在业务执行期间自动续期,业务结束后再释放,代码只需把 tryLock 改成:

// 只设置等待时间,不设置自动释放时间,Redisson会自动续期boolean isLocked = lock.tryLock(5, TimeUnit.SECONDS);

锁的粒度不能太粗也不能太细:比如给 “所有商品的库存” 用一把锁(key=“inventory:lock”),会导致所有商品的扣减请求都排队,性能太差;但如果给 “每个库存变更操作” 都用不同的锁,又会导致锁太多,管理复杂。建议按 “业务 + 唯一标识”(如商品 ID、用户 ID)设计锁 key,平衡性能和可靠性。

集成简单:Spring Boot3+Redisson starter,依赖 + 配置两步搞定,不用手动管理 Redis 连接和锁的底层逻辑;用法灵活:支持可重入锁、公平锁、读写锁等多种锁类型,满足不同业务场景;可靠性高:自动续期、死锁预防、线程安全释放,解决了 Redis 原生锁的诸多痛点。

最后想跟同行们说:分布式锁看似简单,但实际项目中 “超卖”“重复下单” 这些问题,很多时候都是锁的细节没处理好导致的。如果你在 Spring Boot3 集成 Redisson 的过程中,遇到了版本兼容、锁失效、性能瓶颈等问题,欢迎在评论区留言分享 —— 咱们一起讨论解决方案,少踩坑、多提效!

来源:从程序员到架构师一点号

相关推荐