首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >学习进度为什么老不同步?多端进度同步的排查实战

学习进度为什么老不同步?多端进度同步的排查实战

原创
作者头像
数字化落地笔记
发布于 2026-10-28 09:42:12
发布于 2026-10-28 09:42:12
1510
举报

学习进度为什么老不同步?多端进度同步的排查实战

先说结论:学员在手机看了一半、电脑继续看却从头开始,这类进度不同步的根因是进度写入没有幂等与版本控制——多条进度同时上报,落后的覆盖了新的。解法是给每次进度更新加"版本号+时间戳",服务端只接受新版本,配合防抖合并高频上报。本文从一个 3000 学员在线平台的真实故障讲起,给出可落地的同步方案与代码。

背景交代下环境:给一家在线职业教育平台做学习进度改造,底座用的是某一站式SaaS教育系统,课程、章节、学员、后台管理这些基础能力它都承载;但"进度同步的一致性"这种跟业务强耦合的规则,通用能力覆盖不到,在业务层自己实现。故障就发生在我们自研的进度同步逻辑上,与底座本身无关。

事故还原:3000 学员同时上课,进度乱了

平台接入一场万人直播课后,收到大量投诉:手机看到第 12 节,电脑打开还是第 3 节;昨天看完的课,今天显示"未完成"。查日志发现根因:进度上报接口没有幂等保护,学员的 APP 和网页端同时上报进度,旧的上报(第 3 节)晚到,把新的(第 12 节)覆盖了。进度被"回退",比"没记录"更伤体验。

第一步:先分清是"没同步"还是"回退"

进度不同步分两类,排查方向完全不同:

  • 一类:根本没同步。A 端上报了,B 端读不到。查接口与存储——上报失败了?还是多端读的不是同一个存储?
  • 二类:同步了但被覆盖(回退)。两条进度都入库了,但旧值覆盖了新值。查写入逻辑——有没有幂等、有没有版本判断?

判断方法:用两个终端分别看到第 3 节和第 12 节,看数据库里最终存的是哪条。存了旧的就是二类,没存的就是一类。

第二步:进度上报要"幂等 + 版本号"

进度同步的核心是只让新的进度生效。做法:每次进度携带版本号(或"用户+课程+章节"的业务唯一键 + 学习时长),服务端判断"新版本才覆盖":

代码语言:python
复制
def update_progress(user_id, course_id, chapter_no, version):
    # 用业务唯一键做幂等:同一用户同一课程同一章节只保留一条
    key = f'progress:{user_id}:{course_id}'
    # 版本号小的上报直接丢弃,只接受更大的版本
    if version <= get_current_version(key):
        return 'stale_ignored'
    set_current_version(key, version)
    db.save(user_id, course_id, chapter_no, version)
    return 'updated'
  • 幂等:同一上报重复发多少次,结果都一样(不会重复计、不会叠加);
  • 版本号:旧的覆盖不了新的——这是进度不回退的关键;
  • 版本号可以直接用"学习进度百分比"或"章节序号"充当,不一定要单独字段。

第三步:高频上报要"防抖合并"

学员看视频时,播放器可能每秒都在上报进度。全量入库扛不住,还会互相打架。两个手段:

  • 前端防抖:5 秒内最多上报一次,变化超过阈值(如新看 30 秒)才上报;
  • 后端合并:短时间内的多次上报,只保留最新一次入库,不逐条写。
代码语言:javascript
复制
// 播放器上报进度:5秒防抖 + 变化超阈值才上报
let lastReport = 0;
player.on('timeupdate', (e) => {
  const now = Date.now();
  if (now - lastReport < 5000) return;        // 5秒内不重复上报
  const pct = Math.floor(e.percent * 100);
  if (Math.abs(pct - lastPct) < 5) return;     // 变化小于5%不上报
  lastReport = now; lastPct = pct;
  reportProgress(courseId, chapterNo, pct, version++);
});

实测:防抖合并后,单节课的高频上报量降为原来的 1/40,接口压力骤减,进度也不乱了。

第四步:多端读取要走"最新优先"

读取时也要注意:如果 APP 和网页端各有本地缓存,必须以服务端最新进度为准,别让本地缓存把进度"卡住":

代码语言:python
复制
# 读取进度:服务端数据为准,本地缓存仅作展示
def get_progress(user_id, course_id):
    return db.query_latest(user_id, course_id)  # 只读服务端最新一条

教训:早期版本网页端先读本地 localStorage,服务端更新了也显示旧的,学员以为"没同步"。一律服务端为准,本地只缓存展示层。

第五步:上线前的验证清单

  • 两个终端同时看同一课,互相交替上报,验证最后显示的是较大进度(版本号生效);
  • 同一上报连发 10 次,验证只入库一次(幂等生效);
  • 播放器快进快退 1 分钟,验证上报频率被防抖压住;
  • 断网重连后继续看,验证进度从断点继续(断点续传);
  • 服务端读进度,验证永远返回最新一条。

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

  • 坑1:进度没有版本号。旧上报覆盖新上报,进度回退。必须"只接受新版本";
  • 坑2:上报不幂等。同一进度重复上报,完成状态被来回翻转。业务唯一键 + 版本号双保险;
  • 坑3:读本地缓存优先。服务端已更新,本地缓存还在显示旧的。一律服务端为准;
  • 坑4:高频上报全量入库。每秒一次写库,接口打爆。前端防抖 + 后端合并。

FAQ

Q1:版本号用什么值?

答:最简单的用学习进度百分比(0-100)或章节序号——越大约新。复杂场景再单独加 version 字段。

Q2:多端同时上报会不会冲突?

答:会。但版本号机制下,两个端各自带当前版本,服务端只接受大的那个——谁新听谁的,不冲突。

Q3:进度同步需要 WebSocket 实时推吗?

答:不需要。学员切端是低频事件,用"下次打开时拉取最新进度"就够;实时推反而增加复杂度和成本。

Q4:断网期间看的进度会丢吗?

答:不会丢,但要"本地暂存 + 联网补报"。前端把断网期间的进度暂存,恢复网络后带上最新版本号补报一次。

Q5:版本号和时间戳都要吗?

答:版本号是主判据(谁新听谁),时间戳做兜底参考(同时上报时谁后到听谁)。两个字段成本很低,建议都存,排查问题时能直接定位"哪条覆盖了哪条"。

结语

学习进度不同步,九成是"旧值覆盖新值"。版本号 + 幂等 + 防抖合并,三件套做到,进度回退归零、高频上报不炸库。这套方案不只在教育场景有效——任何"多端写同一个进度/状态"的业务(课程进度、文档协作、任务状态)都能直接套用。

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

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

目录
  • 学习进度为什么老不同步?多端进度同步的排查实战
    • 事故还原:3000 学员同时上课,进度乱了
    • 第一步:先分清是"没同步"还是"回退"
    • 第二步:进度上报要"幂等 + 版本号"
    • 第三步:高频上报要"防抖合并"
    • 第四步:多端读取要走"最新优先"
    • 第五步:上线前的验证清单
    • 踩坑清单(4条真实踩过)
    • FAQ
    • 结语
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档