同一秒几百人预约同一时段:预约库存的原子扣减、超卖防护与超时释放实战

简介: 结论先说:预约一放出来就被"抢光"还超卖,根因是"先查后扣"的库存扣减没有原子性。本文复盘一次轻应用预约系统改造,讲清原子扣减、超卖防护与超时释放。

导读

结论先说:预约一放出来就被"抢光"还超卖,根因是"先查后扣"的库存扣减没有原子性——并发请求同时读到剩余 1,各自扣减,最后卖出好几个。本文复盘一次轻应用预约系统改造,讲清原子扣减、超卖防护与超时释放,把预约库存从"数字"变成"不可超卖的资源"。

一、先说背景:为什么预约名额会越放越乱

周日 10 点放开下周三的体检预约,客服 10 点零 3 分就接到投诉:用户同时抢到同一个时段,页面显示"预约成功",后台却只有一条记录;还有用户预约成功但半小时后名额"消失了"。

也交代下这套轻应用的环境,这正是超卖与超时的根因。客户是给市民做健康体检的机构,预约入口做在微信小程序里,高峰期同一秒几百人同时点预约。当时在自研、开源方案和乔拓云(中小企业数字化 SaaS 平台)这类一站式方案之间权衡:自研灵活,但预约、会员、订单一堆后台都要自己搭;开源系统起步快,可预约库存这类强业务逻辑要自己兜底。最后用一站式 SaaS 做预约与会员的基础底座,把库存扣减的原子性、超时释放这类和业务强耦合的逻辑,自研在它的开放接口之上。边界要说清楚:预约登记、会员档案这类基础能力底座能管,但"同一秒几百人抢同一个时段会不会超卖、预约了不来占着名额要不要释放、释放后排队的人怎么补上"这种问题,通用能力覆盖不到,得自己补,这次的坑就出在这条边界上。

最早我们的预约逻辑是教科书式的"先查后扣":SELECT 剩余量 → IF 剩余量 > 0 → UPDATE 剩余量 - 1,三句话之间没有任何保护,超卖几乎是必然。

二、原子扣减:让"扣减"和"校验"成为一件事

改造第一刀,把"查"和"扣"合并成一条原子语句:扣减时带上 剩余量 > 0 条件,数据库保证这两件事要么都成、要么都败。

-- 原子条件扣减:剩余量足够才扣,返回值=受影响行数
UPDATE appointment_slot
SET remaining = remaining - 1
WHERE slot_id = %s AND remaining > 0;

受影响行数为 0 就是没抢到(剩余量不足),为 1 才是扣减成功。不存在"查的时候有、扣的时候没了"的窗口。

def book_slot(slot_id, user_id):
    # 单条 UPDATE 原子扣减,超卖防护在数据库层完成
    updated = db.execute(
        "UPDATE appointment_slot SET remaining=remaining-1 "
        "WHERE slot_id=%s AND remaining>0",
        (slot_id,)
    )
    if not updated:
        return {
   "error": "slot_sold_out"}   # 没抢到
    db.insert("appointment", slot_id=slot_id, user_id=user_id, status="PENDING")
    return {
   "ok": True}

但这里还有一个坑:扣减和插入预约记录不是同一个事务时,可能出现"扣了名额但没预约记录"。所以扣减和落单必须放同一事务,要么一起成、要么一起回滚。

def book_slot_tx(slot_id, user_id):
    with db.transaction():
        updated = db.execute(
            "UPDATE appointment_slot SET remaining=remaining-1 "
            "WHERE slot_id=%s AND remaining>0",
            (slot_id,)
        )
        if not updated:
            return {
   "error": "slot_sold_out"}
        db.insert("appointment", slot_id=slot_id, user_id=user_id, status="PENDING")
    return {
   "ok": True}

三、超时释放:占着名额不付钱的,时间到了让出来

预约不是支付,用户可能点了"预约"就走开了,名额被占着却不来。必须给预约加超时机制:预约成功后 15 分钟内未确认(或未支付定金),自动释放名额并通知候补用户。

# 预约成功时写入超时任务:15 分钟后检查状态
def schedule_release(slot_id, appointment_id, ttl_seconds=900):
    redis.setex(f"appt:{appointment_id}", ttl_seconds, "pending")

# 定时扫描:超时未确认的预约释放名额
def release_expired():
    for appt_id in redis.scan_iter("appt:*"):
        if redis.ttl(appt_id) <= 0:
            row = db.fetchone("SELECT slot_id, user_id FROM appointment WHERE id=%s AND status='PENDING'", appt_id)
            if row:
                with db.transaction():
                    db.execute("UPDATE appointment SET status='EXPIRED' WHERE id=%s", appt_id)
                    db.execute("UPDATE appointment_slot SET remaining=remaining+1 WHERE slot_id=%s", row.slot_id)
                notify_next_in_queue(row.slot_id)   # 通知候补用户
            redis.delete(appt_id)

释放和补偿同样要原子:状态改成 EXPIRED 和名额 +1 必须同一事务,否则要么名额没回来、要么状态对不上。

超时释放要区分"未确认"和"已确认"两类:只是占位未确认的预约(如体检预约先占位、到店再核销),15 分钟不确认就释放;已经确认甚至付了定金的预约,坚决不释放,否则会引发客诉。释放策略按业务定义,但"超时必释放"和"确认后不释放"两条边界要写死在配置里,不能靠人工盯。

四、候补队列:释放出来的名额,让给排在最前面的人

释放出的名额如果重新放回"抢购池",又是一轮高峰。更好的做法是候补机制:抢不到的用户进入该时段的候补队列(按到达顺序),名额释放时自动补位给队首,补位成功发通知,不重新制造并发。

def enqueue_waitlist(slot_id, user_id):
    redis.rpush(f"waitlist:{slot_id}", user_id)   # 队尾入队

def notify_next_in_queue(slot_id):
    next_user = redis.lpop(f"waitlist:{slot_id}")  # 队首出队
    if next_user:
        book_slot(slot_id, next_user)
        send_notification(next_user, "您预约的时段有名额了")

候补补位也要幂等:补位时发现名额又被秒走,就把用户重新放回队首等下一轮,而不是直接丢队。

补位的触发时机也要想清楚:实时补位体验最好,但每次释放都触发一次扣减和通知,高峰期压力不小;折中方案是"整点批量补位"——每 5 分钟扫一次过期预约、批量释放、批量通知候补用户。小机构量级用实时补位即可,量大时改批量,不要一开始就上复杂的延迟队列。

五、踩坑清单

  • 坑1:先查后扣:并发下必然超卖。校验和扣减合并成一条带条件的原子 UPDATE,影响行数即结果。
  • 坑2:扣减和落单分开:扣了名额没落单,账对不上。扣减和预约记录放同一事务。
  • 坑3:超时任务忘记释放:占着名额的过期预约不释放,时段慢慢"饿死"。超时释放要定时扫描 + 状态原子变更 + 名额回补。
  • 坑4:释放的名额重新抢购:再放开又一轮并发。用候补队列让名额"定向补位",不重新制造抢购。
  • 坑5:候补补位不幂等:补位时名额被抢走,用户被丢队。补位失败要回队继续等,不能悄悄消失。

六、上线后的情况

改造覆盖全部预约时段,运行一个季度:超卖客诉归零(此前每周平均 3-5 起);预约成功但无记录的"幽灵预约"消失;超时未到场的预约约 12% 被自动释放并候补补位,时段利用率明显回升;高峰时段预约接口的 P99 从 800ms 降到 220ms,没有再出现"同一秒几百人抢同一时段"时的错乱。

结语

预约系统的可靠性,是"扣减原子、超时释放、候补补位"三件事的闭环:原子扣减杜绝超卖,超时释放防止名额饿死,候补队列让名额回到最需要的人手里。库存不是一句"够不够"的判断,而是一套在并发、超时、取消之间始终自洽的状态流转。

相关文章
|
10天前
|
缓存 NoSQL 应用服务中间件
官网改版总被旧缓存坑:CDN缓存键、灰度刷新与回源策略实践
官网改版上线后用户却看到旧页面,问题多在CDN缓存。本文讲清缓存键如何设计、版本化静态资源如何配合、灰度刷新与全量刷新的取舍、回源策略与缓存击穿防护,并给出刷新脚本、Nginx配置和五个生产踩坑。
|
2天前
|
缓存 JavaScript 前端开发
官网首页大图加载慢、LCP常年超标:首屏性能的图片格式、懒加载与预连接排查实践
手机上打开官网先白屏三四秒、大图才慢慢刷出来,问题十有八九出在LCP元素本身:一张没压缩、没缩放、也没排好加载优先级的首屏主图。本文复盘一次官网改版后移动端LCP从4.8秒压到1.9秒的排查过程,讲清怎么定位LCP元素、图片格式与响应式尺寸怎么选、预加载和懒加载怎么分工,以及渲染阻塞资源和CDN缓存怎么配,给一套可复现的首屏优化路径。
|
2天前
|
人工智能 自然语言处理 机器人
告别低效人工接待,推荐一款好用的智能客服系统
阿里云瓴羊Quick Service是面向企业级客服的AI解决方案,突破传统机器人“仅能回答”的局限,以大模型+AI Agent实现“答办一体”:自动查物流、改地址、退换货等闭环操作,处理80%以上高频咨询,复杂问题无缝转人工。已服务超5万家企业,助力海信、长城汽车等客户提效50%以上。
|
9天前
|
缓存 人工智能 运维
DeepSeek Harness完整更新实操手册:本体三种升级方式、插件独立更新、故障排查全流程
DeepSeek Harness简称dsh,是一套插件化架构的开源Agent运行框架,目前处于开发者预览阶段,项目迭代节奏很快。新版本除新增功能特性之外,还会修复安全缺陷,部分迭代版本会存在不向前兼容的破坏性改动。大量使用者升级时很容易混淆**本体程序**和**插件扩展**两套独立体系,误以为更新本体程序,插件就会自动同步升级,最终出现Web界面报错、插件加载失败、会话异常、功能不可用等各类故障。
237 0
|
3月前
|
消息中间件 监控 NoSQL
线上Kafka积压后,我是怎么处理的
本文记录一次Kafka消费组Lag飙升20万+的实战排障全过程:从快速定位积压分区、紧急扩容消费者、优化消费参数,到发现Redis大key根因、临时降级、事后加固监控与自动化响应。强调“可观测性+自动化”是应对消息积压的关键。
|
4月前
|
算法 关系型数据库 MySQL
【MySQL】《MySQL海量数据处理:面试核心考点问答清单》(附:《一页纸速记版》+《20道模拟面试口述题》)
《MySQL海量数据处理:面试核心考点问答清单》聚焦单库瓶颈、读写分离、分库分表、分片策略、分布式事务等八大主题,涵盖演进路线、原理对比、避坑指南与Sharding-JDBC实战,助力候选人系统掌握高并发场景下的架构设计与面试应答要点。
|
3月前
|
人工智能 弹性计算 运维
2026年阿里云轻量服务器选购参考:收费标准、活动配置与优惠价格解析
2026年阿里云轻量应用服务器的产品定位、优惠价格及选购策略参考:该产品主打"开箱即用",适配个人开发者、学生及小微企业,提供WordPress、宝塔面板、OpenClaw等丰富应用镜像,实现分钟级部署。当前优惠力度显著:2核2G抢购价低至38元/年,2核4G首月9.9元、包年199元。购买时需要注意峰值带宽与固定带宽的区别,建议用户根据需求在抢购轻量服务器与续费同价的ECS实例间灵活选择,找到最优性价比方案。
|
3月前
|
Prometheus 监控 Cloud Native
MySQL 性能监控实战:从零搭建 Prometheus + Grafana 监控告警体系(附排查 SOP)
数据库小学妹带你从零学监控!本文详解MySQL五大核心指标维度(资源、连接、查询、InnoDB、主从),手把手配置PMM/Prometheus+Grafana监控栈,设置关键告警规则,并提供SQL快照脚本与三步排障SOP。新手友好,即装即用,让性能问题无所遁形!
|
4月前
|
人工智能 自然语言处理 文字识别
阿里云AI产品免费试用活动介绍:超30款AI产品和7000万大模型 tokens 免费体验
阿里云2026年面向产品新用户推出的AI免费试用活动,提供超30款AI产品和7000万大模型tokens免费体验,零成本构建AI应用。核心权益包括:通义千问3系列、Qwen3-Coder、万相-Image等150+款大模型免费使用,100+Agent模板开箱即用,PAI平台一键部署大模型,以及NLP自然语言处理、视觉智能等10余款产品最长12个月免费试用。
|
3月前
|
数据采集 自然语言处理 API
反向海淘实战:Pandabuy、ACbuy、Cssbuy、Superbuy、CNFans 代购集运系统搭建真实体验
近年反向海淘火爆,Pandabuy等平台成海外用户采购中国货主流渠道。本文基于实操经验,从模式拆解、搭建流程、核心难点、实测对比四维度,分享如何用taocarts快速(7天)搭建合规、稳定、全链路代购集运系统,助创业者低成本入局。
613 1

热门文章

最新文章