预约 是高频需求,但很多团队初期会用 "MySQL 直连 + 同步流程" 快速落地,后续却难免踩坑:
早高峰抢预约时库存超卖、接口响应卡到几秒、节假日调休要改代码重启
。我在项目中基于 SpringBoot+RabbitMQ+Redis,做了套轻量优化方案:既搞定了库存准确性、接口速度、节假日维护这些核心痛点,又不用额外学习重技术。

一、传统单个系统设计的核心不足
在未优化的单个 SpringBoot 系统中,印章预约功能会因 "同步阻塞、数据竞争、规则僵化" 导致一系列问题,具体如下:
- 库存超卖 :全靠 MySQL 直接扣减库存,高并发下多线程同时修改同一库存,易出现 "
库存为负
"(比如库存 1,两人同时预约都成功),业务规则失效。 - 节假日维护繁琐 :节假日、调休规则硬编码在代码中(如if (date == "2024-10-01")),
每次调休需改代码
、重启系统,重启期间功能不可用。 - 定时任务阻塞业务:每日初始化库存的定时任务(如 20:00 更新未来库存)若 "全量一次性执行",会占用系统主线程,导致用户预约时接口卡顿甚至超时。
- 接口响应慢 :预约流程中同步执行
"查库存 + 生成订单 + 发通知 + 写日志"
,单接口耗时常超 1 秒,用户体验差。 - 数据库压力大:查可预约日期、库存等高频操作直接访问 MySQL,高峰期(如早 8 点抢预约)数据库 QPS 被打满,系统整体卡顿。
二、优化后的方案设计(核心:RabbitMQ 异步解耦 + 分层控制)
1. 整体逻辑
单个系统内通过 "定时任务预加载 + Redis 缓存提速 + RabbitMQ 异步削峰 + MySQL 兜底",解决传统方案的痛点,
流程如下:用户选日期→查可预约时段(Redis 缓存)→提交预约→Redis 扣库存→发 MQ 消息→生成订单→MQ 异步处理后续操作(更新 MySQL 库存、发通知)。
2. 核心模块设计(带关键实现说明)
(1)每日 20:00 定时任务:预加载节假日 + 初始化库存**
- 解决问题:节假日规则僵化、定时任务阻塞业务。
- 同步节假日 :调用
阿里云万年历 API
,拉取未来 3 个月的 "日期 + 是否节假日 + 是否调休" 数据,存到sys_holiday表,并缓存到 Redis(供用户查询可预约日期)。 - 初始化库存 :按 "未来 7 天 + 每个时段" 分批生成库存数据(如某时段最多约 5 人,库存设为 5),存到
seal_stock
表,并同步到 Redis(key=seal:stock:{日期}:{时段ID}
)。 - 实现技巧 :用
@Scheduled
定时触发,通过CompletableFuture
分批异步执行(每天库存单独处理),避免阻塞主线程。
(2)库存控制:Redis 原子操作 + MySQL 乐观锁
- 解决问题:库存超卖、数据库压力大。
- 预约前校验 :用户提交预约时,先查 Redis 库存(
GET seal:stock:{日期}:{时段ID}
),若≤0 直接提示 "约满,并且前端打灰"。 - 扣减库存 :
- 用
Redis
的DECR原子操作扣减库存(高并发下保证不超卖); - 扣减成功后,发送 "库存更新" 消息到 RabbitMQ 队列(如
seal.stock.update
); - MQ 消费者接收消息,用 MySQL 乐观锁更新库存表(
UPDATE seal_stock SET remaining_num=remaining_num-1, version=version+1 WHERE ... AND version=?
),保证最终一致性。
- 用
(3)异步解耦:RabbitMQ 处理慢操作
- 解决问题:接口响应慢、同步流程阻塞。
- 核心流程同步化 :仅保留 "Redis 查库存→扣库存→生成订单" 为同步逻辑(耗时≤50ms)。
- 慢操作异步化 :
预约成功后,发送 "订单通知" 消息到RabbitMQ
队列(如seal.notice);
MQ 消费者接收消息,异步执行 "发短信通知、写操作日志" 等耗时操作 (无需用户等待)。
RabbitMQ 关键配置:
javascript
// 1. 生产者:发送消息
@Service
public class AppointmentService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void submitAppointment(...) {
// ... 同步生成订单 ...
// 发通知消息
rabbitTemplate.convertAndSend("seal.notice", orderId);
}
}
// 2. 消费者:处理消息
@Component
public class NoticeConsumer {
@RabbitListener(queues = "seal.notice")
public void handleNotice(Long orderId) {
// 发短信、写日志(异步执行,不阻塞接口)
smsService.send(orderId);
logService.record(orderId);
}
}
(4)缓存提速:Redis 存储高频数据
- 解决问题 :数据库压力大、查询慢。
缓存 "节假日规则"(key=holiday:{日期})、"可预约时段配置"(key=timeSlot:{印章ID})、"实时库存"(key=seal:stock:{日期}:{时段ID})
;
用户查可预约日期、时段时,直接读 Redis,避免频繁访问 MySQL。
(5)防重复预约:MySQL 唯一索引
- 解决问题 :用户误操作导致重复预约。
给seal_appointment
表加唯一索引:UNIQUE KEY uk_user_seal_date (user_id, seal_id, appointment_date)
;
重复提交时,MySQL 会报 "唯一索引冲突",系统捕获异常并提示 "已预约,无需重复操作"。
三、方案优势
- 无超卖:Redis 原子操作 + MySQL 乐观锁,双重保障库存准确性;
- 接口快:核心流程同步 + RabbitMQ 异步解耦,响应时间压缩至 50ms 内;
- 易维护:节假日自动同步,无需改代码;定时任务分批执行,不阻塞业务;
- 抗并发:Redis 缓存减轻数据库压力,支持高并发场景(如早8 点抢预约,但是我们是zf项目,使用人数还不注意去抢预约,但是这个方案可以用于其他场景);
这篇博客只是讲解优化思路,可以设计的具体代码和详细的数据库设计