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
相关产品推荐
相关产品推荐

