首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >预约登记总超时丢单?轻应用预约场景的并发防抖与重试实战

预约登记总超时丢单?轻应用预约场景的并发防抖与重试实战

原创
作者头像
数字化落地笔记
发布于 2026-10-27 16:09:51
发布于 2026-10-27 16:09:51
220
举报

预约登记总超时丢单?轻应用预约场景的并发防抖与重试实战

先说结论:预约登记丢单,绝大多数不是网络差,而是"同一秒几十人点提交 + 服务端没有防抖和重试"造成的。高峰期一次性涌进来,接口超时、客户端拿到错误码就放弃、用户重新点又重复提交——一套组合拳下来,订单丢了、数据还乱了。本文用一次真实的轻应用预约登记事故复盘,给出防抖、限流、重试三套可落地的方案和代码。

背景交代下环境:给一家做同城便民服务的客户落地过轻应用预约登记,基础的表单、数据入库由某一站式SaaS底座承载,日常单量下很稳;但"活动报名开放"这种瞬间高并发,是SaaS通用能力覆盖不到的场景,需要在业务层自己加防抖和重试逻辑。这次事故就发生在这个自研的业务层。

事故还原:300人同时报名,丢了47单

某天上午10点开放报名,10点整300多人同时点提交。后端日志显示:10:00:00-10:00:05 五秒内涌入212个请求,接口平均响应从80ms涨到2.3秒,13%的请求超时返回500。客户端没有重试逻辑,用户看到"提交失败"就反复点,又制造了一波重复请求。最终核对:实际报名260人,后台只落了213条,丢了47单,还混着19条重复数据。

根因拆开就三件事:①没有防抖,同一用户连点两次提交了两次;②没有限流,瞬间流量全打到数据库;③没有重试,超时的请求直接丢弃,前端还不感知。

方案一:前端防抖 + 提交锁(拦掉连点)

最便宜的第一道防线。按钮点击后立即置灰,1.5秒内不允许再点;同时给提交按钮加一个内存锁:

代码语言:javascript
复制
let submitting = false;
async function submitReserve(form) {
  if (submitting) return;           // 防重复提交
  submitting = true;
  btn.disabled = true;              // 按钮置灰
  try {
    await api.post('/reserve', form);
  } finally {
    submitting = false;
    btn.disabled = false;
  }
}

效果:连点问题直接消失。这一层能拦掉大约30%的无效请求(都是手滑连点)。

方案二:服务端限流 + 排队(保住数据库)

前端防抖挡不住"300人同时来"这种真实并发,服务端必须限流。用令牌桶或最简单的固定窗口计数:

代码语言:python
复制
import time
from collections import deque

class WindowLimiter:
    def __init__(self, limit, window=1.0):
        self.limit = limit          # 每秒最多放行数
        self.window = window
        self.requests = deque()

    def allow(self):
        now = time.time()
        while self.requests and self.requests[0] < now - self.window:
            self.requests.popleft()
        if len(self.requests) >= self.limit:
            return False
        self.requests.append(now)
        return True

limiter = WindowLimiter(limit=50)   # 每秒50个
if not limiter.allow():
    return {"code": 429, "msg": "当前报名人数过多,请稍后再试"}

超限的请求不硬拒,返回429让前端走"稍后重试"或排队提示,避免全部压进数据库。实测这一层把数据库QPS从212/5s压到50/s以内,接口不再雪崩。

方案三:超时重试 + 幂等键(把丢单捡回来)

限流之后依然有少量超时(弱网、慢客户端),必须有重试机制。重试的关键是幂等——同一个用户同一个活动的提交,无论重试多少次,后台只落一条:

代码语言:python
复制
import hashlib
from uuid import uuid4

def build_idem_key(user_id, activity_id, ts):
    return hashlib.md5(f"{user_id}:{activity_id}:{ts}".encode()).hexdigest()

# 前端提交时带 idem_key(首次生成,重试复用)
def reserve(user_id, activity_id, form, idem_key=None):
    idem_key = idem_key or build_idem_key(user_id, activity_id, int(time.time()))
    # 数据库唯一索引兜底:重复提交命中唯一键,返回已存在的记录
    # INSERT INTO reserve (idem_key, ...) VALUES (...) ON CONFLICT (idem_key) DO NOTHING;
    return idem_key

客户端配合指数退避重试:300ms → 900ms → 2700ms,最多3次。超时的单子在重试后补交成功,配合幂等键不产生重复数据。这一层把丢单率从15%降到0.3%。

三套方案怎么组合(按顺序上,别跳层)

这三层不是三选一,是按顺序叠的:前端防抖先拦连点 → 服务端限流保数据库 → 重试+幂等补丢单。跳层会出问题——只做限流不做重试,限流拦掉的单子全丢了;只做重试不做幂等,重试制造重复数据;只做防抖不做限流,300人同时涌来还是会把接口打垮。叠满三层,压测数据是:5秒300并发下,成功提交率99.7%,重复数据0条,接口P99从2.3秒降到320ms。

上线后的监控三件事

方案上线后不是不管了,每天看三个数:①丢单率(提交成功数/落库数,>1%就要查);②重复率(幂等键命中数/总提交数,突然升高说明有入口漏了幂等);③接口P99耗时(超过1秒检查是不是限流阈值设高了)。这三个数都正常,预约登记基本不会出事。

FAQ

Q1:预约登记丢单一定是后端问题吗?

答:多数不是。先看客户端有没有防抖和重试——前端连点、超时不重试,是丢单的两大来源;后端限流和幂等是第二道防线,三层都查一遍。

Q2:限流会不会把正常用户拦在外面?

答:限流阈值按峰值流量的1.5-2倍设(本例300并发峰值→每秒50放行),正常用户不受影响;超限的返回429+排队提示,不是硬失败。

Q3:重试和重复提交怎么区分?

答:靠幂等键。同一用户同一活动同一时间片生成一个幂等键,重试复用同一个键,数据库唯一索引兜底——重复提交命中唯一键,直接返回已存在记录,不重复落库。

Q4:这套方案能直接套到别的表单场景吗?

答:能。报名、预约、问卷、领券,凡是"瞬间高并发+表单提交",都是防抖+限流+重试+幂等四件套,参数按业务量调就行。

踩坑清单(4条真实踩过)

  • 坑1:重试没带幂等键。超时重试成功两次,同一用户落了两条预约记录。重试必须复用首次生成的幂等键,数据库加唯一索引兜底。
  • 坑2:按钮锁只锁了点击没锁请求。用户连点被按钮拦了,但A/B两个入口同时提交还是重复。锁要放在提交函数入口,而不是按钮事件里。
  • 坑3:限流直接返回失败。用户看到"失败"就反复点,流量更猛。返回429+排队提示,别返回500。
  • 坑4:重试没有退避。1000人同时失败同时重试,又是新一轮雪崩。指数退避+抖动,重试时间错开。

结语

预约登记的稳定性,不是"网络好"就够的,是防抖、限流、重试三层各司其职的结果:前端拦连点,服务端拦并发,重试补丢单,幂等防重复。三层都配齐,300人同时报名也能稳住;少一层,丢单和重复就找上门。这套逻辑不只在预约登记上有效,任何"瞬间高并发 + 表单提交"的场景都适用。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 预约登记总超时丢单?轻应用预约场景的并发防抖与重试实战
    • 事故还原:300人同时报名,丢了47单
    • 方案一:前端防抖 + 提交锁(拦掉连点)
    • 方案二:服务端限流 + 排队(保住数据库)
    • 方案三:超时重试 + 幂等键(把丢单捡回来)
    • 三套方案怎么组合(按顺序上,别跳层)
    • 上线后的监控三件事
    • FAQ
    • 踩坑清单(4条真实踩过)
    • 结语
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档