You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot电影订票系统按查询参数过滤共享资源更新并发请求实现

问题解答

是否有Spring默认提供的注解可以实现需求

Spring 以及 Spring Boot 官方没有提供开箱即用的注解来实现该业务场景的并发控制规则。这类和具体业务逻辑强绑定的优先级调度、并发更新过滤逻辑,需要基于通用并发工具手动实现。

最优实现方案(新手友好落地版)

核心思路

针对单场次座位资源加锁,所有请求拿到锁后按noOfSeats优先级判断是否允许执行,同优先级仅放行一个请求更新资源。
如果是单体部署的系统,直接用JDK自带的本地锁即可实现,无需引入额外组件;如果是集群部署的系统,替换为Redis分布式锁即可,业务逻辑完全不变。

代码实现示例

首先引入Guava工具包提供的分段锁降低锁粒度(不同电影/场次的请求互不影响,性能远高于全局锁),Maven依赖如下:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.1-jre</version>
</dependency>

接口实现代码:

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.locks.Lock;
import com.google.common.util.concurrent.Striped;

@RestController
public class MovieTicketController {
    // 按[电影ID:场次ID]分配独立锁,最多1024个锁,足够平衡性能和内存占用
    private static final Striped<Lock> SEAT_RES_LOCKS = Striped.lock(1024);
    // 座位业务服务,负责查询/更新座位状态、优先级请求标记
    private final SeatService seatService;

    public MovieTicketController(SeatService seatService) {
        this.seatService = seatService;
    }

    @PostMapping("/movies")
    public String bookMovieSeats(
            @RequestParam String movieId,
            @RequestParam String showId,
            @RequestParam Integer noOfSeats
    ) {
        // 构造唯一资源key,对应单场电影的座位资源
        String resourceKey = movieId + ":" + showId;
        Lock resourceLock = SEAT_RES_LOCKS.get(resourceKey);
        // 加锁保证同一时间只有一个请求进入判断逻辑
        resourceLock.lock();
        try {
            // 1. 先判断当前场次是否已经完成预订处理,已处理直接拒绝所有后续请求
            if (seatService.isBookingCompleted(movieId, showId)) {
                return "当前场次所选座位已被预订";
            }
            // 2. 查询当前已记录的最高优先级请求的座位数
            Integer currentMaxSeats = seatService.getMaxRequestedSeats(movieId, showId);

            // 3. 优先级判断
            if (noOfSeats < currentMaxSeats) {
                return "存在更高优先级的预订请求,本次请求无法处理";
            } else if (noOfSeats.equals(currentMaxSeats)) {
                // 同优先级用原子更新保证仅一个请求通过:尝试把maxSeats+1,更新成功的请求获得处理权限
                if (!seatService.tryIncrementMaxSeats(movieId, showId)) {
                    return "同优先级请求过多,本次请求未被选中";
                }
            } else {
                // 当前请求优先级更高,更新最高座位数为本次请求的值
                seatService.updateMaxRequestedSeats(movieId, showId, noOfSeats);
            }

            // 4. 执行座位预订逻辑
            seatService.doSeatBooking(movieId, showId, noOfSeats);
            // 5. 标记该场次预订已完成,拦截后续所有请求
            seatService.markBookingCompleted(movieId, showId);
            return "座位预订成功";
        } finally {
            // 释放锁
            resourceLock.unlock();
        }
    }
}

补充说明

  • 上述SeatService中的状态(maxRequestedSeats、bookingCompleted)如果存在数据库中,需要把查询+更新操作包装为数据库事务保证原子性;如果追求更高性能,直接存在Redis中即可。
  • 集群部署场景下,仅需要把上述本地StripedLock替换为Redis分布式锁,业务判断逻辑不需要做任何修改,Spring Boot集成Redis可以直接用官方提供的spring-boot-starter-data-redis启动器。

内容的提问来源于stack exchange,提问作者Chintu Karthi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 07:27:03