预约优化方案全链路优化实践

预约 是高频需求,但很多团队初期会用 "MySQL 直连 + 同步流程" 快速落地,后续却难免踩坑:早高峰抢预约时库存超卖、接口响应卡到几秒、节假日调休要改代码重启

我在项目中基于 SpringBoot+RabbitMQ+Redis,做了套轻量优化方案:既搞定了库存准确性、接口速度、节假日维护这些核心痛点,又不用额外学习重技术。

一、传统单个系统设计的核心不足

在未优化的单个 SpringBoot 系统中,印章预约功能会因 "同步阻塞、数据竞争、规则僵化" 导致一系列问题,具体如下:

  1. 库存超卖 :全靠 MySQL 直接扣减库存,高并发下多线程同时修改同一库存,易出现 "库存为负"(比如库存 1,两人同时预约都成功),业务规则失效。
  2. 节假日维护繁琐 :节假日、调休规则硬编码在代码中(如if (date == "2024-10-01")),每次调休需改代码、重启系统,重启期间功能不可用。
  3. 定时任务阻塞业务:每日初始化库存的定时任务(如 20:00 更新未来库存)若 "全量一次性执行",会占用系统主线程,导致用户预约时接口卡顿甚至超时。
  4. 接口响应慢 :预约流程中同步执行 "查库存 + 生成订单 + 发通知 + 写日志",单接口耗时常超 1 秒,用户体验差。
  5. 数据库压力大:查可预约日期、库存等高频操作直接访问 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 直接提示 "约满,并且前端打灰"。
  • 扣减库存
    1. Redis 的DECR原子操作扣减库存(高并发下保证不超卖);
    2. 扣减成功后,发送 "库存更新" 消息到 RabbitMQ 队列(如seal.stock.update);
    3. 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项目,使用人数还不注意去抢预约,但是这个方案可以用于其他场景);

这篇博客只是讲解优化思路,可以设计的具体代码和详细的数据库设计

相关推荐
nice_lcj5202 小时前
深入理解ArrayList与LinkedList:Java集合框架核心对比(含实战案例+面试考点)
java·面试
小蕾Java2 小时前
IDEA快速上手指南!
java·intellij-idea·swift
聪明的笨猪猪2 小时前
Java 内存模型(JMM)面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
羚羊角uou2 小时前
【Linux】线程的互斥
java·开发语言
学编程的小鬼2 小时前
SpringBoot日志
java·后端·springboot
用户4099322502122 小时前
PostgreSQL备份不是复制文件?物理vs逻辑咋选?误删还能精准恢复到1分钟前?
后端·ai编程·trae
HWL56792 小时前
输入框内容粘贴时   字符净化问题
前端·vue.js·后端·node.js
来不及辣哎呀2 小时前
学习Java第三十天——黑马点评37~42
java·开发语言·学习
IT_陈寒3 小时前
「JavaScript 性能优化:10个让V8引擎疯狂提速的编码技巧」
前端·人工智能·后端