Redis 分布式锁实战:Spring Boot 3 步搞定「超卖 / 重复报名」(毕设答辩加分项)

技术栈:Spring Boot 2.7 + Redis + Redisson(或原生 SET NX)

场景:活动报名、秒杀、实验室预约、选课——同一资源被多人同时抢

结论:这是本科毕设里「性价比最高」的技术亮点之一——代码少、原理清、演示效果好。


一、为什么这个功能能让答辩老师眼前一亮?

很多毕设都是 CRUD:增删改查走一遍,老师已经看腻了。

但如果你能在答辩时说:

“高并发下,我用 Redis 分布式锁解决了重复报名问题,并通过压测验证了正确性。”

老师立刻会多问你两句——这就是亮点

而且实现难度可控:

  • 不需要微服务、不需要 Kafka;

  • 一个 Redis + 几十行 Java 代码就能跑;

  • 论文里能写:并发场景、锁原理、测试对比。


二、问题本质:并发下的「超卖」

活动报名为例:


剩余名额:1

用户 A、B 同时点击「报名」

如果没有锁,两个请求可能同时读到「名额=1」,都判断通过,都写入数据库——超卖


时间线:

T1  A 读名额=1  ✓

T2  B 读名额=1  ✓

T3  A 扣减,写入成功

T4  B 扣减,写入成功  ← 本该失败

数据库层面可以用 UPDATE ... WHERE stock > 0,但毕设里加一层 Redis 锁:

  1. 答辩更好讲(分布式、并发);

  2. 可以演示「加锁前后对比」;

  3. 论文多一个「关键技术」章节。


三、方案选型:Redisson vs 原生 Redis

| 方案 | 优点 | 缺点 |

|------|------|------|

| SET key NX EX 原生 | 无额外依赖,原理透明 | 需自己处理续期、可重入 |

| Redisson | 开箱即用、看门狗自动续期 | 多一个依赖 |

毕设推荐 Redisson:稳定、代码短、文档多。


四、环境准备

1)引入依赖

<!-- Redis -->

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-data-redis</artifactId>

</dependency>

<!-- Redisson -->

<dependency>

    <groupId>org.redisson</groupId>

    <artifactId>redisson-spring-boot-starter</artifactId>

    <version>3.23.5</version>

</dependency>

2)application.yml

spring:

  redis:

    host: 127.0.0.1

    port: 6379

    database: 0



redisson:

  singleServerConfig:

    address: redis://127.0.0.1:6379


五、核心代码(可直接抄进毕设)

1)报名 Service:加锁 + 业务

@Service

@RequiredArgsConstructor

public class ActivitySignupService {



    private final ActivityMapper activityMapper;

    private final SignupMapper signupMapper;

    private final RedissonClient redissonClient;



    /**

     * 活动报名 —— 分布式锁保护临界区

     */

    @Transactional(rollbackFor = Exception.class)

    public Result signup(Long activityId, Long userId) {

        String lockKey = "lock:activity:signup:" + activityId;

        RLock lock = redissonClient.getLock(lockKey);



        try {

            // 等待最多 3 秒,锁持有最多 10 秒自动释放

            boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);

            if (!locked) {

                return Result.fail("系统繁忙,请稍后重试");

            }



            // ===== 临界区开始:以下逻辑同一时刻只有一个线程执行 =====



            // 1. 查活动剩余名额

            Activity activity = activityMapper.selectById(activityId);

            if (activity == null) {

                return Result.fail("活动不存在");

            }

            if (activity.getRemainQuota() <= 0) {

                return Result.fail("名额已满");

            }



            // 2. 防重复报名

            if (signupMapper.existsByActivityAndUser(activityId, userId)) {

                return Result.fail("您已报名,请勿重复提交");

            }



            // 3. 扣减名额 + 写入报名记录

            int rows = activityMapper.decreaseQuota(activityId);

            if (rows == 0) {

                return Result.fail("报名失败,名额已满");

            }

            signupMapper.insert(new Signup(activityId, userId, LocalDateTime.now()));



            // ===== 临界区结束 =====

            return Result.ok("报名成功");



        } catch (InterruptedException e) {

            Thread.currentThread().interrupt();

            return Result.fail("报名被中断");

        } finally {

            if (lock.isHeldByCurrentThread()) {

                lock.unlock();

            }

        }

    }

}

2)Mapper:乐观扣减(双保险)

// ActivityMapper.java

@Update("UPDATE activity SET remain_quota = remain_quota - 1 " +

        "WHERE id = #{id} AND remain_quota > 0")

int decreaseQuota(@Param("id") Long id);

为什么两层?

  • Redis 锁:减少打到 DB 的并发冲突,答辩好讲;

  • SQL 条件更新:最后一道防线,防止极端情况下超卖。


六、原生 Redis 实现(不用 Redisson 的版本)

想论文里写「手写分布式锁原理」可以用这个:


@Component

@RequiredArgsConstructor

public class RedisLockUtil {



    private final StringRedisTemplate redisTemplate;



    /**

     * SET key value NX EX seconds

     * 只有 key 不存在时才设置成功 → 获取锁

     */

    public boolean tryLock(String key, String value, long expireSeconds) {

        Boolean ok = redisTemplate.opsForValue()

                .setIfAbsent(key, value, Duration.ofSeconds(expireSeconds));

        return Boolean.TRUE.equals(ok);

    }



    /**

     * 释放锁:先比对 value 再删,防止误删别人的锁

     */

    public void unlock(String key, String value) {

        String current = redisTemplate.opsForValue().get(key);

        if (value.equals(current)) {

            redisTemplate.delete(key);

        }

    }

}

答辩常问:

  • Q:锁过期了业务还没跑完怎么办?

A:Redisson 看门狗自动续期;手写版可设合理过期时间 + 业务尽量短。

  • Q:Redis 挂了怎么办?

A:降级为数据库乐观锁;毕设场景可说明「本系统面向校园小规模并发,Redis 单点足够」。


七、怎么在答辩里演示「真的有用」?

方法 1:JMeter / Postman 并发测试
  1. 活动名额设为 10

  2. 用 JMeter 开 50 个线程 同时请求报名接口;

  3. 看数据库:报名记录是否正好 10 条

  4. 去掉锁再测一次 → 可能 >10 条,对比截图放 PPT。

方法 2:前端连点

写个简单脚本或浏览器控制台连发 10 次请求,展示「只有 1 次成功,其余提示已报名/名额满」。

论文测试章节模板:

| 用例 | 并发数 | 名额 | 预期 | 实际(加锁) | 实际(无锁) |

|------|--------|------|------|--------------|--------------|

| 重复报名 | 50 | 10 | 成功10 | 成功10 | 成功13(超卖) |


八、和其他毕设场景的快速迁移

| 原场景 | 锁 Key 设计 | 临界区逻辑 |

|--------|-------------|------------|

| 活动报名 | lock:activity:{id} | 查名额 → 扣减 → 写记录 |

| 实验室预约 | lock:lab:{labId}:{timeSlot} | 查冲突 → 写预约 |

| 选课 | lock:course:{courseId} | 查容量 → 选课记录 |

| 秒杀 | lock:seckill:{productId} | 查库存 → 扣库存 → 订单 |

改一个 key + 改业务逻辑,就能套到新题目上。


九、论文里怎么写才显得专业?

关键技术章节结构建议:

  1. 问题描述:并发场景下的超卖 / 重复提交

  2. 方案对比: synchronized(单机) vs Redis 分布式锁(多实例)

  3. 实现原理:SET NX、锁过期、Redisson 看门狗(可配图)

  4. 核心代码:加锁范围、finally 释放

  5. 测试验证:并发测试数据对比

别写: 「采用了先进的 Redis 技术提升系统性能」——太空。

要写: 「在活动报名接口引入 Redisson 分布式锁,保证名额扣减与报名记录写入的原子性。」


十、引流区:想要完整 Redis 锁模块 + JMeter 测试脚本?

评论区留言:

  • Redis分布式锁 + 语言(Java) + 你的场景(报名/预约/选课)

例如:

  • Redis分布式锁 Java 活动报名

我会发:

  • Redisson 完整配置 + Service 模板

  • JMeter 并发测试步骤截图版

  • 论文「并发控制」章节写作模板

  • 答辩 5 个高频追问 + 标准答法


十一、最后一句

Redis 分布式锁是毕设里**少数「代码不多、原理清楚、答辩能讲、测试能证」**的技术点。

加一个锁,你的项目就从「管理系统」升级成「考虑过并发问题的系统」。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐