Spring Boot 实战:基于 Redis 的分布式锁设计与优化

🚀 Spring Boot 实战:基于 Redis 的分布式锁设计与优化

💡 “同一商品库存被多个请求同时扣减,怎么防止超卖?”
今天我们用 Spring Boot + Redis 实现一个真正可靠的 分布式锁方案

关键词:Spring BootRedis分布式锁高并发


📖 一、背景:为什么要用分布式锁?

在高并发场景中,多个请求同时操作同一份资源(例如库存、订单号、优惠券),可能导致:

  • ❌ 库存被重复扣减(超卖)
  • ❌ 同一个用户重复下单
  • ❌ 订单数据不一致
    在这里插入图片描述

在单体应用中,我们用 synchronizedReentrantLock 即可解决并发访问问题。
但在 分布式架构 下,这些锁只在单个 JVM 生效,根本无法跨节点同步。

👉 因此我们需要:分布式锁(Distributed Lock)
Redis 因为高性能、原子性强支持过期时间,成为分布式锁的主流实现方式。


🧠 二、Redis 分布式锁的基本原理

在这里插入图片描述

✅ 核心命令:

SET key value NX EX 30

解释:

  • NX:仅当 key 不存在时才设置(防止重复上锁)
  • EX 30:设置锁的过期时间(30 秒自动释放)
  • value:用来标识锁的持有者(UUID)

🧩 解锁逻辑:

  1. 比较锁中的 value 是否为当前线程的 UUID;
  2. 一致才执行 DEL
  3. 防止误删他人加的锁。

⚙️ 三、项目结构设计

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 实现可重入分布式锁与看门狗机制详解》。🔥

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT小哥哥呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值