终端断网一上午,恢复后数据怎么不重不丢:本地队列、断网续传与冲突合并

简介: 从一台终端断网三小时、恢复后补传数据出现重复、丢失和互相覆盖说起,讲清离线优先数据同步的落地:本地 oplog 操作日志结构、clientOpId 幂等键与单调 seq 序号、按序小批量断点续传、服务端去重与缺口检测,以及多端并发修改时的版本号、字段级合并、最后写入获胜(LWW)与冲突标记,附五个踩坑。

有个门店场景的终端装在信号不稳定的位置,运营商线路割接那天上午直接断网三个多小时,但终端不能停,店员照常录入业务记录。等网络恢复,问题来了:补传的数据有的重复进了两遍,有的干脆丢了,还有几条在断网期间被另一台终端改过、合并时互相覆盖。这其实是典型的离线优先(offline-first)数据同步问题,跟具体业务无关,核心是本地队列、幂等补传和冲突合并。这篇把这套机制的落地过程记下来。

一、思路:断网时写本地,恢复后按队列补传

不能等网络可用才让用户操作,而是把终端本地当成可靠的第一写入点:每条记录产生时先落本地存储并标记状态,网络可用时再按顺序补传到服务端。

每条本地记录维护一个操作日志(oplog)结构:

{
  "clientOpId": "c_8a3f_1694_0001",   // 终端生成的全局唯一操作ID,幂等键
  "seq": 2049,                          // 本终端单调递增的操作序号
  "type": "upsert_record",
  "payload": { ... },                  // 具体数据
  "createdAt": 1726450000000,
  "status": "pending"                  // pending / synced / conflict
}

两个标识各有用途:clientOpId 用于服务端幂等去重,同一操作补传多少次都只生效一次;seq 用于本终端操作排序和断点续传,告诉服务端"我已经同步到第几号",漏传的能被发现。这套离线同步机制是在唯顿收银系统的终端上落地的,我主要负责本地 oplog 设计、补传幂等和冲突合并这几块,做完之后断网半天再恢复,数据也能对齐。

二、补传:幂等键 + 顺序 + 断点续传

补传不是把本地数据一股脑重发,而是有明确的去重和续传规则。

服务端以 clientOpId 做幂等:建唯一约束,已存在的 clientOpId 直接返回成功但不重复落库。这样终端因为超时、重试、重启而重复发送同一条操作,服务端都能识别。

// 服务端处理一条补传(伪代码)
async function applyOp(op) {
  const existed = await db.opLog.findOne({ clientOpId: op.clientOpId });
  if (existed) return { duplicated: true, status: existed.status };  // 幂等返回

  // 校验 seq 连续性,发现缺口说明有操作漏传
  const lastSeq = await db.opLog.maxSeq(op.deviceId);
  if (op.seq > lastSeq + 1) {
    return { needGap: true, fromSeq: lastSeq + 1 };   // 要求终端补传缺口段
  }
  await db.opLog.insert({ ...op });
  await applyToBusiness(op.payload, op.version);
  return { duplicated: false, status: "synced" };
}

终端侧补传按 seq 升序、小批量(比如每批 50 条)顺序提交,一批全部成功才把本地状态改成 synced;中途失败从最后一个未确认的 seq 续传,而不是从头再来。本地存储要选落盘可靠的方案(终端上用 SQLite 或带持久化的本地库),避免应用被杀掉后 pending 记录丢失。

三、冲突合并:版本号 + 最后写入获胜 + 冲突标记

多台终端或终端与后台同时改同一条记录时,需要一套确定性的冲突规则。我用的是"版本号 + 最后写入获胜(LWW)+ 冲突兜底":

服务端更新逻辑:
1. 每条记录带 version(整数)和 updatedAt;
2. 终端更新时携带它读到的 baseVersion;
3. 若 baseVersion === 服务端当前 version,直接更新,version + 1;
4. 若 baseVersion < 服务端 version,说明期间被别人改过:
   - 字段级可合并的(不同字段),自动合并;
   - 同一字段都被改,按 updatedAt 较新者胜出(LWW);
   - 无法安全合并的,置为 conflict 状态并保留两份,交后台规则或人工确认。
// 字段级合并示意
function merge(serverDoc, clientDoc, baseVersion) {
  if (clientDoc.baseVersion === serverDoc.version) return { ...clientDoc, version: serverDoc.version + 1 };
  const merged = { ...serverDoc };
  for (const key of Object.keys(clientDoc.changes)) {
    const sTime = serverDoc.fieldUpdatedAt[key] || 0;
    const cTime = clientDoc.fieldUpdatedAt[key] || 0;
    if (cTime >= sTime) merged[key] = clientDoc.changes[key];   // 较新者胜
  }
  merged.version = serverDoc.version + 1;
  return merged;
}

纯 LWW 会静默丢数据,所以对"同一关键字段被双方修改"这种情况,我没有让它无声覆盖,而是标记 conflict 并保留双方内容,宁可让人确认一次,也不丢一条记录。

四、我踩过的五个坑

  1. 补传没有幂等键:超时重试导致同一条记录落两遍,加 clientOpId 唯一约束后彻底解决;
  2. 只靠 updatedAt 去重:终端时钟不准会错乱,幂等交给服务端生成约束、时钟只用于 LWW 比较;
  3. 一次性全量重发:断网半天积压上千条,整包重发又慢又容易超时,改成按 seq 小批量顺序补传;
  4. 冲突一律后写覆盖:同字段被改时静默丢了较早的修改,加版本号判断和 conflict 标记后才安全;
  5. pending 状态只放内存:终端重启后待补传记录丢失,oplog 必须落盘并在启动时扫描恢复。

五、几个容易忽略的细节

  • 同步状态机要显式区分 pending、synced、conflict,UI 上让用户看得到"还有多少条没同步";
  • 补传要处理终端时钟回拨和跨天积压,排序一律以服务端确认结果为准;
  • 删除操作也要进 oplog(软删除标记),否则断网期间删的记录恢复后又冒出来;
  • 建议在服务端按 deviceId 记录已同步水位,方便排查"终端说发了、服务端没收到"的纠纷。

复盘要点

  • 离线优先的关键是"本地可靠落盘 + 全局幂等键 + 单调序号 + 小批量顺序补传",让重复发送无害、漏传可发现;
  • 冲突不要无脑后写覆盖,版本号做并发检测、字段级合并加 LWW、不安全就标记保留;
  • oplog 覆盖增删改全部操作并持久化,是断网恢复后数据不重不丢的底座。

以上是个人实践记录,各平台具体功能以官方实时信息为准。

你们做弱网或断网场景的终端数据同步时,冲突是怎么处理的?是直接最后写入获胜,还是做了字段级合并?

相关文章
|
5月前
|
安全 C语言 Perl
博途 TIA Portal V21 仿真设计软件 安装详细教程 附安装包
TIA Portal V21(博途)新一代全集成自动化工程软件,支持PLC编程、HMI组态、运动控制、安全、通信与仿真,专为工业4.0和数字化工厂设计。含完整安装教程及离线下载链接。(238字)
5812 0
|
1天前
|
IDE 开发工具
额度翻倍!还有四天
Qoder推出Sonus模型专属福利:9月16-19日,付费用户每日12:00可领1000 Credits,用于全球顶级Sonus模型——支持编程、长程任务、专业软件操作及公式表格处理。登录Qoder各端活动入口即可领取。
86 0
|
2天前
|
人工智能 缓存 监控
阿里云百炼Token Plan个人版与团队版深度解析:Credits计费规则,个人团队版对比与落地实操指南
Token Plan是阿里云百炼推出的订阅制AI算力套餐,核心设计思路是**Credits统一计量**,不再单独区分输入Token、输出Token,而是将模型调用、工具调用、多模态生成任务统一折算成Credits进行消耗抵扣。无论是Qwen系列模型、DeepSeek系列模型,还是图片生成、视频生成、语音能力,全部在同一个Credits额度池内扣减,这也是该订阅方案最核心的优势。**详情👉[访问阿里云百炼Token Plan服务页面](https://www.aliyun.com/benefit/scene/tokenplan?userCode=t1dwdo7u)了解**。
53 4
|
2天前
|
数据采集 数据管理 大数据
数据治理-重点、方法、目标(1)
20年数据老兵坦言:数据开发、数仓、大数据驾轻就熟,唯数据治理屡感挫败。重读DAMA2后反思:治理核心非文档工具,而是“决策权与问责机制”;落地关键不在顶层设计,而在找准业务痛点——数据质量才是最强驱动力。
46 2
|
2天前
|
人工智能 安全 网络安全
日本 2026 年上半年勒索软件攻击态势与防控研究
本文基于日本警察厅2026年上半年数据,分析勒索软件攻击新态势:案件达123起历史新高,VPN设备成主要入侵入口,中小企业与制造业受害最重。研究揭示攻击前置探测激增、恢复周期长、成本高昂等困境,并提出覆盖设备加固、中小企业赋能、跨国协作与AI检测的四维治理路径。(239字)
26 1
|
6天前
|
人工智能 自然语言处理 定位技术
告别AI出题重复又超纲:题目去重、难度分级与知识点标注的配置实践
记录AI自动出题配置:题目去重与相似度判重、难度分级与知识点标注、题库冷启动,含5个踩坑案例。
|
6天前
|
缓存 自然语言处理 网络协议
告别垃圾邮件塞满收件箱:多层反垃圾与IP信誉过滤的配置实践
记录企业邮箱多层反垃圾配置:SPF、DKIM、DMARC校验、贝叶斯过滤与黑白名单、退信排查,含5个踩坑案例。
|
2天前
|
缓存 人工智能 自然语言处理
通义千问Qwen大模型全系列模型深度解析:能力矩阵、行业落地、选型定价与API实操完整教程(Max/Plus/Flash/Coder/Omni)
随着生成式AI从单点Demo走向企业规模化落地,很多团队在选型大模型时容易陷入困惑:不知道该选用旗舰模型还是轻量模型,分不清文本、视觉、全模态、编程模型各自适用场景,不清楚不同档位模型的价格差异,也不熟悉API集成、上下文缓存、批量推理等工程化手段。通义千问Qwen并非单一模型,而是一套分层完整的大模型家族,覆盖旗舰复杂推理、均衡长文本、高并发轻量任务、代码开发、图像视频理解、全模态音视频交互等多个品类,同时提供在线API调用、开源本地部署两种方案。本文将完整梳理Qwen全系列模型能力矩阵,拆解每一类模型的技术特点、适用业务场景,介绍金融、制造、政务、电商、软件研发等行业落地案例,详细讲解按量
232 0
|
1天前
|
SQL Java 测试技术
接口变慢先别加机器:一次 P99 抖动的压测定位与连接池调优
从一次 P50 正常、P99 却飙到两三秒的接口抖动说起,讲清为什么先看分位数而不是急着加机器:用 wrk 分档压测配合分阶段埋点,定位到请求在等数据库连接而非算力不足;再记录连接池大小估算、HikariCP 的 maximumPoolSize、connectionTimeout、maxLifetime、keepaliveTime 调参,N+1 慢查询治理,以及线程池有界队列与降级,附瓶颈对比表和五个踩坑。
|
15小时前
|
人工智能 中间件 API
LangChain+Llama.cpp 本地模型与Agent工具调用
本文介绍如何用LangChain对接本地llama.cpp服务:通过`llama-server`启动Qwen量化模型,配置OpenAI兼容接口;安装LangChain生态包;实现基础对话与自定义工具Agent(如查天气、时间),全程离线运行,零依赖云端API,适合私有化AI原型开发。(239字)
37 3