🚀 Spring Boot 实战:基于 Redis 的分布式锁设计与优化
💡 “同一商品库存被多个请求同时扣减,怎么防止超卖?”
今天我们用 Spring Boot + Redis 实现一个真正可靠的 分布式锁方案。关键词:
Spring Boot、Redis、分布式锁、高并发
📖 一、背景:为什么要用分布式锁?
在高并发场景中,多个请求同时操作同一份资源(例如库存、订单号、优惠券),可能导致:
- ❌ 库存被重复扣减(超卖)
- ❌ 同一个用户重复下单
- ❌ 订单数据不一致

在单体应用中,我们用 synchronized 或 ReentrantLock 即可解决并发访问问题。
但在 分布式架构 下,这些锁只在单个 JVM 生效,根本无法跨节点同步。
👉 因此我们需要:分布式锁(Distributed Lock)。
Redis 因为高性能、原子性强、支持过期时间,成为分布式锁的主流实现方式。
🧠 二、Redis 分布式锁的基本原理

✅ 核心命令:
SET key value NX EX 30
解释:
NX:仅当 key 不存在时才设置(防止重复上锁)EX 30:设置锁的过期时间(30 秒自动释放)value:用来标识锁的持有者(UUID)
🧩 解锁逻辑:
- 比较锁中的 value 是否为当前线程的 UUID;
- 一致才执行
DEL; - 防止误删他人加的锁。
⚙️ 三、项目结构设计
redis-lock-demo/
┣━ src/
┃ ┣━ main/
┃ ┃ ┣━ java/com/example/redislock/
┃ ┃ ┃ ┣━ controller/
┃ ┃ ┃ ┣━ service/
┃ ┃ ┃ ┗━ config/
┃ ┃ ┗━ resources/
┣━ pom.xml
┗━ Dockerfile (可选)
🧩 四、核心依赖配置
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
application.yml
spring:
redis:
host: localhost
port: 6379
🧱 五、封装 Redis 分布式锁工具类
RedisLockUtil.java
package com.example.redislock.util;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Component
public class RedisLockUtil {
private final StringRedisTemplate redisTemplate;
public RedisLockUtil(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
// 加锁
public String tryLock(String key, long expireTime) {
String value = UUID.randomUUID().toString();
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success) ? value : null;
}
// 解锁
public void unlock(String key, String value) {
String currentValue = redisTemplate.opsForValue().get(key);
if (value.equals(currentValue)) {
redisTemplate.delete(key);
}
}
}
🔧 六、服务层逻辑(库存扣减业务)
StockService.java
package com.example.redislock.service;
import com.example.redislock.util.RedisLockUtil;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class StockService {
private int stock = 10; // 模拟库存
private final RedisLockUtil redisLockUtil;
public StockService(RedisLockUtil redisLockUtil) {
this.redisLockUtil = redisLockUtil;
}
public String reduceStock() {
String lockKey = "lock:stock";
String lockValue = redisLockUtil.tryLock(lockKey, 5);
if (lockValue == null) {
return "⛔ 系统繁忙,请稍后再试";
}
try {
if (stock > 0) {
TimeUnit.MILLISECONDS.sleep(200); // 模拟耗时
stock--;
return "✅ 扣减成功,剩余库存:" + stock;
} else {
return "❌ 库存不足";
}
} catch (Exception e) {
return "异常:" + e.getMessage();
} finally {
redisLockUtil.unlock(lockKey, lockValue);
}
}
}
🧩 七、控制层接口
StockController.java
package com.example.redislock.controller;
import com.example.redislock.service.StockService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class StockController {
private final StockService stockService;
public StockController(StockService stockService) {
this.stockService = stockService;
}
@GetMapping("/reduce")
public String reduce() {
return stockService.reduceStock();
}
}
🧪 八、实战项目演示:防止库存超卖 & 重复下单
💼 业务场景:
电商项目中,多个用户同时点击“立即购买”,请求会瞬间并发。
若不加锁,10 个库存可能会被扣成负数。
🧰 环境准备:
- Spring Boot 启动端口:8080
- Redis 本地运行:6379
- 并发测试工具:ApacheBench 或 JMeter
🚀 测试指令:
ab -n 50 -c 20 http://localhost:8080/reduce
📊 测试结果(输出示例):
✅ 扣减成功,剩余库存:9
✅ 扣减成功,剩余库存:8
...
❌ 库存不足
⛔ 系统繁忙,请稍后再试
🔍 观察点:
- 并发请求下,库存不会变为负数;
- Redis 中锁键
lock:stock会在执行中存在,任务结束后自动删除; - 锁竞争时部分请求直接返回“系统繁忙”,防止数据库冲突。
⚡ 九、优化与进阶
| 问题 | 优化方向 |
|---|---|
| 锁过期时间过短可能提前释放 | 使用 Redisson 实现自动续期机制 |
| 单节点 Redis 故障 | 采用 Redlock 多节点锁 |
| 多业务锁管理混乱 | 使用前缀命名规范,如 lock:order:、lock:coupon: |
🔒 十、Redisson 实现自动续期锁(进阶版)
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.2</version>
</dependency>
@Autowired
private RedissonClient redissonClient;
public String reduceStockV2() {
RLock lock = redissonClient.getLock("lock:stock");
try {
if (lock.tryLock(100, 10, TimeUnit.SECONDS)) {
if (stock > 0) {
stock--;
return "✅ 扣减成功,剩余库存:" + stock;
} else {
return "❌ 库存不足";
}
} else {
return "⛔ 系统繁忙,请稍后再试";
}
} catch (Exception e) {
return "异常:" + e.getMessage();
} finally {
lock.unlock();
}
}
✅ Redisson 提供自动续期机制(看门狗机制),避免执行时间过长导致锁提前释放。
🧭 十一、总结
| 模块 | 技术点 |
|---|---|
| 分布式锁核心 | Redis SET NX EX |
| 解锁机制 | 比较 value 再删除 |
| 优化实现 | Redisson 自动续期 |
| 实战场景 | 防止库存超卖、防止重复下单 |
| 可扩展性 | 支持多节点、高可用部署 |
💬 结语
你在项目中用过哪些防止超卖的方案?
是用 Redis 锁、数据库悲观锁 还是 消息队列削峰?
在评论区说说你的做法👇
下一篇我来写《Redisson 实现可重入分布式锁与看门狗机制详解》。🔥
1711

被折叠的 条评论
为什么被折叠?



