分时预约系统的库存模型:为什么'加个预约功能'必然出现超卖与假满

做票务系统的同学大概都遇到过这个需求:"我们景区要上分时预约,加个预约功能就行。"

如果真按"加个功能"来做,上线后大概率会收到三类工单:某时段显示约满但现场没满;同一身份证约到了两个时段;退票之后名额没有回到可约池。

这不是业务没想清楚,而是库存模型选错了。

直接答案

分时预约的库存不是标量,而是一个三维矩阵:时段 × 票种 × 身份占用状态。把矩阵当标量("还剩 N 张")来建模,必然出现超卖与假满。工程上的正解是"分时段独立库存 + 预占/确认两阶段 + 取消即回池"。

一、为什么标量库存会失败

传统售票系统的库存模型很朴素:stock 是一个整数,下单减一,退票加一。

这个模型在"一次入园"场景下没问题,但在分时预约下会同时踩三个坑:

  1. 跨时段超卖 :stock 是全天的总数,10 点的名额被约光后,系统仍允许约 11 点,因为总数还有余量。
  2. 并发超卖 :stock-- 是非原子操作时,高并发下会击穿。
  3. 假满:取消订单只加回总数,没有加回对应时段的余量,于是"总量还有,时段约不到"。

根因:分时预约的约束不是"总量",而是"每个时段的余量",而后者是一个带维度的量。

二、正解:三维库存 + 两阶段提交

2.1 库存维度

css 复制代码
stock[time_slot][ticket_type] = 剩余可约数

每个时段独立库存,独立校验。跨时段不共享余量。

2.2 两阶段:预占(reserve)与确认(commit)

下单不直接扣库存,而是先"预占":

  • reserve:在目标时段预占 1 个名额,写一条带 TTL 的预占记录(例如 15 分钟)。
  • commit:支付成功后转正,预占记录变为已占用。
  • release:超时未支付或用户取消,预占记录过期自动释放,余量秒级回池。

这一步是"假满"的解药。没有 TTL 预占的系统,订单和库存是两套账,迟早对不上。

2.3 并发控制

  • 余量扣减用原子操作(Redis DECR / 数据库 UPDATE ... WHERE stock > 0)。
  • 强一致场景用分布式锁或队列串行化,把"同一时段"的扣减收敛到单点。

三、核销与状态的闭环

预约系统里,订单状态比库存更容易出错。建议的状态机:

scss 复制代码
预占 → 已支付(未核销) → 已核销
       ↘ 已取消(退款) → 库存回池
       ↘ 已过期(未核销未取消) → 库存回池

"已过期"这一支最容易被漏掉。 未核销又没取消的订单,如果一直占着名额,就是"死库存"。

核销端要满足两个工程要求:

  1. 离线核销:景区门口网络最差,核销端要能本地缓存名单,断网时先验后同步。
  2. 幂等核销:同一张票重复扫码不能重复计数,用核销流水号去重。

四、渠道与风控:让抢到的票无法交付

技术代抢的本质是"解析接口 + 批量占位"。工程上的应对不是封 IP,而是提高"变现成本":

  • 订单与证件强绑定:抢到的票无法转让,倒卖即失效。
  • 设备指纹 + 行为频次:异常账号进入风控队列,人工复核。
  • 预占限流:单账号未支付预占数量上限,避免"占坑"。

中新网 2026 年 8 月报道过一起案件:有人利用软件抢票一年半、抢下两万余张、非法获利近 600 万元,最终以非法获取计算机信息系统数据罪获刑。技术代抢的法律风险是真实的,但更有效的防线是让票无法交付。

五、验收清单(工程视角)

# 验收项 工程判据
1 库存维度 stock[时段][票种] 独立,不共享
2 预占机制 有 TTL,过期自动释放
3 并发安全 原子扣减或串行化
4 状态机 含"已过期"分支,死库存可回收
5 核销 支持离线 + 幂等
6 退改 退款与库存回池同一事务
7 风控 订单证件强绑定 + 频次限制
8 数据导出 全量订单/核销可导出

六、FAQ

Q:用消息队列削峰就够了吗? 不够。削峰解决的是流量,不解决库存维度错误。维度错了,削峰只是把错误推迟。

Q:预占 TTL 设多长合适? 通常 10-15 分钟,结合支付渠道的回调时效。太短会误释放,太长会压库存。

Q:历史数据要不要迁移? 要。系统锁没锁死,不看价格,只看一句话:三年后想换供应商,这些数据能不能完整带走。

库存不是一个数,而是一个带时间的矩阵。 核销端能不能离线验票,才是票务系统的成人礼。 模型错了,限流只是把排队搬到了线上。

写在最后

分时预约不是售票系统的一个功能开关,而是一次库存模型的替换:从"还剩多少张"到"哪个时段还剩多少、被谁占着、什么状态"。

模型对了,限流才是限流;模型错了,限流只是把排队搬到了线上。

相关推荐
Darling噜啦啦1 小时前
# MySQL + Milvus = PostgreSQL?一张表搞定 AI Agent 的长时记忆(附完整代码)
后端·postgresql·orm
用户713874229001 小时前
在 .NET 中使用 AWS Secrets Manager:Config 与 Client 详解
后端
福大大架构师每日一题1 小时前
2026-10-16:交替数列的最大元素。用go语言,给定三个整数 n、s、m。现在要构造一个包含 n 个整数的序列,其中第一个数必须等于 s。序列中相邻数字的
后端
yunwei371 小时前
eBPF 入门实践教程:BPF Token,安全的委托式权限与程序加载
linux·后端·性能优化
136096757231 小时前
旧 SQL 在新 PG 上能不能跑
后端
SamDeepThinking1 小时前
配货单发货数为空告警误报治理:写表延迟复核方案
java·后端·架构
知守观1 小时前
mirrorOf= 把私服请求拦去了公网镜像:本地能跑,CI 上内部包 404 排查
后端·ci/cd·maven
万物智能1 小时前
鸿蒙UART总线怎么用?—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
后端
UrbanJazzerati1 小时前
AutoHotkey v2 零基础快速入门 Part 5 游戏场景明暗阈值采集工具
后端