多设备播放记录同步方案的设计与实现

作者:很酷cat2026.11.04 02:30浏览量:0

简介:本文以播放记录的多端同步为切入点,系统讲解同步架构分层、数据模型设计、基于版本游标的增量同步协议与冲突合并策略,并给出表结构、接口定义与合并伪代码等完整示例,帮助开发者构建一套高效、一致且能从容应对弱网环境的跨设备数据同步体系。

一、问题本质:这不是“小功能”,而是一致性问题

当用户从手机切换到平板打开同一视频时,期望进度无缝衔接。这个看似简单的体验,背后实则是一个典型的多副本数据一致性问题:

  • 多个设备各自持有一份本地数据副本,云端持有一份权威副本,任何一端都可能独立修改;
  • 设备可以离线修改,网络恢复时机不可控,传输顺序也无法保证;
  • 客户端时钟普遍不可信,不能作为全局排序依据;
  • 两台设备同时写入不同进度时,系统必须给出确定性的合并结果,而不是随机覆盖。

因此,播放记录同步的设计目标可以概括为四点:最终一致、合并正确、弱网不丢、规模平滑。下文围绕这四个目标逐层展开。

二、总体架构:本地先行、云端权威、增量双向

一套典型的同步架构可分为四层:

  1. 客户端存储层:使用嵌入式轻量级关系数据库保存全量播放记录、同步游标与脏数据标记。所有写入先落本地,保证离线场景数据零丢失。
  2. 同步引擎层:负责把脏数据打包上传、按游标拉取增量,并统一处理重试、幂等与退避。
  3. 接入层:统一的接口网关承担鉴权、限流与协议版本兼容。
  4. 服务与存储层:同步服务完成服务端合并、写入数据库、更新缓存,并通过消息队列广播变更通知。

数据流向可以概括为:业务写入 → 本地落库 → 同步引擎上传 → 服务端合并并分配版本 → 其他设备增量拉取。客户端永远不直接“写穿”到云端,这是保证离线可用与弱网可恢复的前提。

三、数据模型:几个不能省的字段

核心记录表可按如下方式设计:

  1. CREATE TABLE play_history (
  2. id BIGINT PRIMARY KEY,
  3. user_id BIGINT NOT NULL,
  4. media_id BIGINT NOT NULL,
  5. progress INT NOT NULL DEFAULT 0,
  6. duration INT NOT NULL DEFAULT 0,
  7. is_deleted TINYINT NOT NULL DEFAULT 0,
  8. version BIGINT NOT NULL DEFAULT 0,
  9. updated_at DATETIME(3) NOT NULL,
  10. created_at DATETIME(3) NOT NULL,
  11. UNIQUE KEY uk_user_media (user_id, media_id),
  12. KEY idx_user_version (user_id, version)
  13. );

字段设计的关键考量:

  • version(版本号):由服务端按用户维度单调递增分配,作为增量同步的游标。不要使用数据库自增主键充当游标,因为分库分表后全局自增序列不可控。
  • is_deleted(墓碑标记):删除必须是软删除,并保留一段宽限期(如 30 天)后才物理清理。否则,一台同步滞后的设备会把已被删除的旧记录重新“复活”上传。
  • updated_at(更新时间):必须取服务端接收时间。客户端时间戳只作参考,绝不参与排序判定。
  • 设备维度信息(device_id、change_id)应记录在变更日志中,用于审计追溯与多设备管理,而不是污染主表结构。

四、同步协议:首次全量,此后增量

拉取接口采用游标分页:

  1. POST /v1/sync/pull
  2. {
  3. "device_id": "device-8f3a",
  4. "cursor": 128913,
  5. "limit": 200
  6. }

返回示例:

  1. {
  2. "records": [
  3. {
  4. "media_id": 10086,
  5. "progress": 732,
  6. "duration": 2700,
  7. "is_deleted": false,
  8. "version": 128914
  9. }
  10. ],
  11. "next_cursor": 128914,
  12. "has_more": false,
  13. "server_time": 1730000000000
  14. }

上传接口批量提交本地脏数据:

  1. POST /v1/sync/push
  2. {
  3. "device_id": "device-8f3a",
  4. "changes": [
  5. {
  6. "change_id": "c-20250601-0001",
  7. "media_id": 10086,
  8. "progress": 900,
  9. "client_ts": 1729999998000
  10. }
  11. ]
  12. }

协议设计要点:

  • 首次全量:登录或本地缓存失效时触发全量拉取,按游标分页,避免单次响应过大拖垮弱网环境;
  • 日常增量:只拉取版本号大于本地游标的记录,把带宽消耗压到最低;
  • 幂等上传:change_id 由客户端生成且唯一,网络重试造成的重复提交在服务端直接去重;
  • 按需推送:客户端只提交发生变化的关键字段,而非整条记录。

五、冲突合并:业务语义与时间排序的组合

最典型的冲突是:手机上看到 50%,平板上看到 70%,两台设备几乎同时上线。常见策略分三层:

  1. 最后写入胜出(LWW):按服务端接收时间排序,简单可靠,适用于一般状态字段;
  2. 业务语义合并:进度类字段取最大值——进度通常只前进不后退,较大值更能代表用户真实观看深度;
  3. 墓碑优先:任何删除操作优先级最高,并把版本号置为最大,阻断滞后设备后续的旧写入。

合并逻辑伪代码:

  1. function merge(serverRec, incoming):
  2. if serverRec.is_deleted or incoming.is_deleted:
  3. return deletedRecord(maxVersion)
  4. newProgress = max(serverRec.progress, incoming.progress)
  5. if incoming.duration > 0:
  6. newDuration = incoming.duration
  7. else:
  8. newDuration = serverRec.duration
  9. return record(progress = newProgress,
  10. duration = newDuration,
  11. version = nextVersion(user),
  12. updated_at = serverNow())

需要注意边界:用户刻意从头重看时,“进度取最大”会误判。工程上通常约定客户端在进度回退时附带“重看”标记,服务端收到该标记后允许以较小值覆盖。

六、弱网与离线:本地优先策略

  • 写策略:所有写入立即在本地完成并打上脏标记,用户感知“即时生效”;同步引擎在合适时机批量上传(前台周期触发、切后台触发、网络恢复触发);
  • 重试与退避:上传失败采用指数退避(如 2s、4s、8s,上限 10 分钟)并加入随机抖动,避免大量设备同时恢复网络形成同步风暴;
  • 网络恢复监听:监听连接恢复事件后立即执行一次“先拉后推”,覆盖通勤切换网络等高频场景;
  • 本地裁剪:客户端仅保留最近数千条记录,兼顾存储占用与同步开销。

七、服务端规模与一致性

海量并发下,同步服务要同时扛住读放大与写放大:

  • 热缓存:活跃用户的近期记录放入内存缓存类服务,拉取请求优先命中缓存;缓存可由数据库无损重建,允许短暂降级;
  • 异步落盘:写请求完成内存合并后写入消息队列即返回,消费者批量刷库,以最终一致性换取吞吐;这要求消息持久化可靠,并为消费失败设计兜底回写路径;
  • 分片:按 user_id 哈希分表,保证单个用户的全部记录落在同一分片,冲突合并可在单机内存中完成,无需分布式锁;
  • 冷热分层:热存储仅保留近 90 天数据,更早的历史归档到对象存储类通用服务,平衡成本与查询效率;
  • 可观测性:同步延迟、拉取失败率、队列积压等指标接入日志服务与监控告警。同步类故障往往是“沉默”的——用户换设备时才发现,此时投诉已经发生,必须靠指标提前发现。

若部署在云端基础设施之上(如百度智能云等云厂商普遍提供的计算、存储、消息队列、监控告警等通用类目服务),上述组件可由托管服务拼装而成,团队可把精力更多投入在合并语义与协议演进上。

八、安全与多设备管理

播放记录属于行为隐私数据,多副本同步会放大暴露面:

  • 会话校验:每次同步请求必须携带有效令牌,服务端重新鉴权,绝不信任客户端上报的用户标识;
  • 设备可见可控:用户可查看当前同步中的设备列表并移除异常设备,被移除设备下次同步时清空本地缓存;
  • 删除合规:墓碑宽限期结束后物理清理,账号注销时同步级联清除全部副本;
  • 传输与存储:链路全程加密传输,必要时对敏感字段做脱敏或字段级加密。

九、总结

把多设备播放记录同步拆解为五个可复用的核心决策:

  1. 本地先行写入,离线数据零丢失;
  2. 服务端分配版本号,客户端时间一律不信任;
  3. 游标式增量拉取,全量只在必要时发生;
  4. 按业务语义合并——进度取最大、删除优先;
  5. 缓存、消息队列与分片承接海量读写。

这套决策与具体业务无关,收藏、笔记、阅读进度、游戏存档等多端同步场景均可直接套用。任何产品从单设备走向多设备时,它都是工程成本最低、确定性最强的通用路径。