首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >几百人同时签到怎么不丢不重:班课签到的批量缓冲写入与去重设计

几百人同时签到怎么不丢不重:班课签到的批量缓冲写入与去重设计

原创
作者头像
用户5598620
发布于 2026-09-15 14:01:09
发布于 2026-09-15 14:01:09
1570
举报

直播大班课开课时,几百名学员在同一两秒内点“签到”,线下课几十人同时扫一个码,这类瞬时高写入是教育系统很典型的场景。如果每个请求都立刻逐条 insert、再实时 count 一次出勤人数,峰值一到就会出现请求变慢、同一条签到写两遍、人数和明细对不上。这篇把签到链路从语义到落库讲清楚,给一套不丢、不重、还能抗峰值的写法,文末附可照抄的踩坑清单。

一、瞬时大量签到,直接逐条写会出什么问题

先看最朴素的实现:收到签到请求就往签到表插一行,同时把出勤人数加一。峰值来临时它会同时暴露四类问题:

  • 写锁竞争:大量请求同时写同一张表、更新同一节课这条“课堂记录”,行锁排队,接口延迟被拉长;
  • 重复记录:弱网下客户端自动重试、学员连点按钮,同一节课同一学员被插入多行;
  • 计数漂移:人数靠“加一”维护,一次重试或一次失败就和真实明细出现偏差;
  • 峰值打满:每来一个请求就查一次库、写一次库,连接池在开课瞬间被占满。

问题的根源是把“每次点击”都当成一次必须立刻落库的独立写操作,而签到这种场景天然允许很短的延迟聚合。

二、先定义清楚:“不丢不重”到底指什么

动手前先把验收标准写成三句可检验的话,后面所有设计都围绕它:

1)同一学员、同一节课,签到明细有且只有一条;2)允许请求在很短时间内排队,但每条有效签到最终都要落库,不允许静默丢弃;3)出勤人数一定等于去重后的明细条数,不允许出现两个对不上的数字。

这三句分别对应去重、可靠写入、计数一致,缺一个都会在线上被学员或老师发现。

三、去重的根:业务唯一键加数据库兜底

去重不能只靠前端按钮置灰,也不能只靠缓存判断,最终一定要在数据库层有一条硬约束。签到的业务唯一键是“课节 + 学员”:

代码语言:sql
复制
CREATE TABLE lesson_checkin (
  id          BIGINT PRIMARY KEY AUTO_INCREMENT,
  lesson_id   BIGINT NOT NULL,
  student_id  BIGINT NOT NULL,
  status      VARCHAR(16) NOT NULL COMMENT 'NORMAL正常/MAKEUP补签',
  checked_at  DATETIME NOT NULL,
  client_req_id VARCHAR(64) NOT NULL COMMENT '客户端幂等键',
  UNIQUE KEY uk_lesson_student (lesson_id, student_id),
  UNIQUE KEY uk_req (client_req_id)
);

有了 uk_lesson_student,即使应用层判断失效、两个并发请求同时进来,数据库也只会让一条成功。写入用“冲突即转为已存在”的写法,而不是让接口报错:

代码语言:sql
复制
INSERT INTO lesson_checkin (lesson_id, student_id, status, checked_at, client_req_id)
VALUES (:lesson_id, :student_id, 'NORMAL', NOW(), :req_id)
ON DUPLICATE KEY UPDATE id = id;

ON DUPLICATE KEY UPDATE id = id 命中唯一键时不改动任何数据,接口仍返回“签到成功”,对重复重试天然幂等。再加一个 client_req_id 唯一键,是为了兜住同一客户端的自动重试。应用层校验负责快速失败和友好提示,数据库唯一约束负责绝不出错,两层都要有。

四、抗峰值:服务端批量缓冲、合并写入

去重解决“不重”,批量缓冲解决“不被峰值打垮”。思路是:签到请求先进一个内存聚合区,由后台协程按“凑够一批或到了时间阈值”统一落库,把成百上千次单条写合并成少数几次批量写。

代码语言:python
复制
BUFFER = []           # 待写入缓冲
FLUSH_SIZE = 200      # 凑够 200 条写一次
FLUSH_MS = 500        # 或满 500ms 必写一次

async def on_checkin(req):
    # 先在内存按 课节+学员 去重,重复请求直接返回成功
    key = (req.lesson_id, req.student_id)
    BUFFER.setdefault(key, req)
    if len(BUFFER) >= FLUSH_SIZE:
        await flush()
    return {"ok": True}

async def flush():
    batch = list(BUFFER.values())
    BUFFER.clear()
    # 一条 INSERT 多行,配合唯一索引去重
    await db.executemany(UPSERT_SQL, batch)

批量写入用一条多值 INSERT 配合上一节的唯一索引,数据库往返次数从“每人一次”降到“每批一次”。要让“不丢”成立,必须补两个机制:一是进程关闭或定时阈值到点时强制 flush,避免请求长时间停在内存;二是缓冲去重时保留第一次签到时间,不能被后来的重试覆盖。如果部署了多个服务实例,可以让同一节课的请求按 lesson_id 哈希到同一实例聚合,或借助消息队列分区消费,避免跨实例重复聚合。

五、出勤人数怎么统计才和明细对得上

高峰期不要反复 COUNT 明细表,也不要让独立计数器长期累加(一定会漂移),分两层:

  • 展示层用增量计数:每成功落库一批,就按课节把人数增量加到缓存,供老师实时看“当前已到多少人”;
  • 权威值以明细为准:下课或定时任务对明细表做一次去重统计,回写并校准缓存人数。

实时数字允许短暂近似,但最终口径永远是 SELECT COUNT(DISTINCT student_id) …,两者出现差异时以明细校正计数,而不是反过来。

这样既保住了开课瞬间的流畅,又保证课后导出的出勤表一定准确。

六、弱网重试、补签、替签这些边界

  • 弱网重试:客户端每次进入签到页生成一个 client_req_id,重试沿用同一个键,服务端按它幂等;
  • 补签:补签是另一种状态而不是第二条记录,用 (lesson_id, student_id) 定位原记录后走状态更新,并记录补签时间和操作人;
  • 替签/越权:校验当前学员与签到目标一致,签到令牌一次性、短时效,避免把别人签上;
  • 签到窗口:服务端按课节开始/结束时间判断是否在有效窗口,前端时间不可信。

七、踩坑清单

  • 只在前端做按钮防抖,接口仍可被重试打穿——数据库唯一索引才是最终兜底;
  • 用先查后插判断是否已签到,并发下两条都查不到——直接靠唯一约束 + upsert;
  • 出勤人数用独立计数器长期累加,失败一次就对不上——以明细为准定时校准;
  • 批量缓冲只在凑够数量时 flush,低峰期请求迟迟不落库——加时间阈值兜底;
  • 进程重启不清空缓冲导致内存里的签到丢失——关闭前强制 flush,必要时先写持久队列;
  • 补签又插了一条新记录,明细出现两行——补签走状态更新而非新增;
  • 信任客户端传的时间和身份——窗口校验、一次性令牌都放在服务端。

八、工程落地建议

建议把签到沉淀成一个独立的“高写入采集”模块:接入层负责幂等键校验和缓冲聚合,落库层只认批量 upsert 和唯一约束,统计层区分实时计数与权威明细。缓冲大小、时间阈值、批量条数做成可配置项,按课堂规模压测后取值;大课可以按课节分区消费,让一节课的写入落到同一分区,简化去重。同一套骨架也适用于课堂答题、作业提交、问卷回收等瞬时高写入场景。

九、常见问题

Q:用了批量缓冲,签到会不会有明显延迟?

A:设置“凑够一批或满 500 毫秒必写”的双阈值后,落库延迟通常控制在一秒以内,学员侧表现为点击即成功,真正批量写库在后台完成。

Q:唯一索引和幂等键是不是留一个就够?

A:不够。(课节,学员) 唯一约束保证业务上不重复,client_req_id 保证同一客户端重试不产生副作用,两者分别兜住业务重复和请求重复。

Q:实时出勤人数和课后明细以哪个为准?

A:实时人数来自增量计数、允许短暂近似;课后考勤以去重明细统计为唯一权威值,并用它回写校准实时计数。

十、复盘清单

  • 是否定义了“不丢、不重、计数等于明细”三条可检验标准?
  • 是否用 (课节,学员) 唯一索引 + upsert 做了数据库兜底?
  • 是否用批量缓冲把峰值单条写合并成少量批量写,并加了时间阈值和关闭前 flush?
  • 实时人数是否以明细统计为最终校准口径?
  • 弱网重试、补签、替签、签到窗口是否都做了幂等与服务端校验?

班课签到的难点不在插入一条记录,而在开课瞬间同时守住不丢、不重、计数一致三条线:去重下沉到唯一约束、峰值交给批量缓冲、最终口径交给明细。本文为后端性能实践分享,具体实现以所用数据库、中间件与业务规则为准。

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

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

目录
  • 一、瞬时大量签到,直接逐条写会出什么问题
  • 二、先定义清楚:“不丢不重”到底指什么
  • 三、去重的根:业务唯一键加数据库兜底
  • 四、抗峰值:服务端批量缓冲、合并写入
  • 五、出勤人数怎么统计才和明细对得上
  • 六、弱网重试、补签、替签这些边界
  • 七、踩坑清单
  • 八、工程落地建议
  • 九、常见问题
  • 十、复盘清单
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档