鸿蒙端云一体待办清单:30 秒全量轮询的取舍与增量同步改造分析
背景与问题
今天在鸿蒙开发实践中心读了「云同步待办清单:鸿蒙端云一体实战」案例。它的同步方案是:端侧 ArkTS 应用每 30 秒调用一次 GET /todo/pull,把用户全部任务全量拉回本地重建列表;云侧是 FunctionGraph 的 8 个云函数 + APIG 专享版 + RDS MySQL 三张表(users / sessions / tasks),客户端用 UUID 做任务主键,写入冲突时用 ON DUPLICATE KEY UPDATE 兜底。
这个方案在教学场景下跑得通,也完整覆盖了注册、登录、增删改、搜索和多端同步。但我读完后的第一个问题是:全量拉取能撑多久?任务数从几十条涨到几百条、再多一个设备同时在线时,30 秒一次的全量传输、每次全表 SELECT、每次客户端全量 diff,是不是一种“前期省事、后期还债”的选择?本文就围绕这个取舍展开:先承认全量轮询的合理性,再给出增量改造的最小方案和三条必须守住的边界。
关键分析
先说结论:全量轮询不是偷懒,是教学案例里有意为之的取舍
读案例和仓库代码能看到,全量拉取有三处明确的好处。
- 天然幂等、自愈能力强。客户端任何一次数据丢失、错乱,下一次 pull 就能自动修复;没有游标、没有状态机,出问题时排查路径只有一条:拉一次全量。案例的“高频问题排查”里把“冷启动 504 自动重试 3 次”写得很细,正是这种“拉一次就有”的思路。
- 服务端可以保持无状态。函数里不需要维护游标、版本号或变更日志,tasks 表只需要业务字段。
- 和“客户端 UUID 主键 + ON DUPLICATE KEY UPDATE”的写入策略自洽。多端新建任务不会因为自增 id 冲突;全量拉取再按 id 落库,重复没关系。
代价也很直接。当任务总量到 500+、且多个设备同时轮询时,每 30 秒重复传输全部任务、每次 SELECT * 全表、每次客户端全量 diff,带宽和耗电都线性增长,且与用户实际变更量无关。对教学案例这不是问题,对长期使用的产品就是。
| 维度 | 全量拉取(现状) | 增量拉取(改造后) |
|---|---|---|
| 带宽 | O(全部任务)/30s | O(变更任务)/30s |
| 客户端处理 | 每次全量 diff | 直接 upsert 少量行 |
| 数据库查询 | SELECT * 全表 | WHERE updated_at > ? 走索引 |
| 可扩展性 | 任务 500+ 后明显变慢 | 只与变更量相关,与总量无关 |
增量改造要付出的三个代价
- 不再自愈。全量是天然幂等的;增量一旦游标丢失或错位(比如 since 值被覆盖、客户端清缓存后用错时间点),就可能持续漏拉,且不容易发现。改造后必须给客户端保留一条“全量兜底”路径(比如登录时或每周一次主动拉全量)。
- 写入路径都要维护 updated_at。创建、更新、删除 task 的每个地方都必须正确更新时间戳,漏一个字段,那条数据就永远收不到增量。案例里 8 个函数共享了
parseBody()这样的公共逻辑,更新时间戳可以放在同一个写入出口集中处理,降低漏改概率。 - 删除变复杂。硬删除后增量查询查不到那行,客户端永远不知道要删除它。这是我认为最大的改动点,必须把删除改成软删除(见下)。
方案或实现
改造的最小切面是三处:表结构、拉取接口、客户端游标。
1. 表结构:一列时间戳 + 一个墓碑字段
ALTER TABLE tasks
ADD COLUMN updated_at TIMESTAMP(3) NOT NULL
DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
ADD COLUMN deleted_at TIMESTAMP(3) NULL,
ADD INDEX idx_updated_at (updated_at);
为什么用 TIMESTAMP(3) 而不用秒精度的 DATETIME:MySQL 的 DATETIME 默认精度是秒,同一秒内的两次更新会被 updated_at > since 漏掉。毫秒精度把碰撞概率压得很低(仍非零,所以下面还要处理)。
删除改为软删除:
UPDATE tasks SET deleted_at = NOW(3), updated_at = NOW(3) WHERE id = ?;
客户端收到 deleted_at IS NOT NULL 的行,就在本地删除该任务。墓碑记录可以定期清理(比如 30 天后物理删除),但同步窗口内必须保留——否则客户端在窗口期外又收不到删除信号。
2. 拉取接口:只加一个 since 参数,不动原有结构
GET /todo/pull
GET /todo/pull?since=2026-11-12T03:20:00.123Z
服务端:
SELECT * FROM tasks
WHERE user_id = ? AND updated_at >= ?
ORDER BY updated_at, id;
注意这里我用 >= 而不是 >:同一毫秒内两次更新仍可能碰撞,>= 会让客户端对边界行做重复接收(幂等落库去重,成本是一次多余 upsert),而 > 一旦漏掉就再也追不回来。在“宁可重复、不可漏掉”的取舍里,我选 >=。
另外,案例原有的设计(parseBody 解码 APIG 的 base64 body、dateStrings + getBeijingTime() 消除 8 小时时差)在改造里一个都不用动——时间戳比较完全发生在服务端,客户端不参与时间计算。
3. 客户端游标:只信服务端返回的 sync_token
这是最容易踩的坑:since 绝不能取自客户端本地时钟。设备时钟可能不准、可能被用户改过、时区和服务端也可能不同。
正确流程是:
- 第一次:
GET /todo/pull,服务端返回全部任务 +sync_token = max(updated_at); - 后续:
GET /todo/pull?since=<上一次返回的 sync_token>; - 如果本次结果为空,服务端返回当前
NOW(3)作为游标推进,避免因为“没有变更”导致客户端的 since 永远停在原地。
同时要强调:拉取冲突的归属校验不变。案例设计里“每条写操作都用会话 token 校验归属”,这个校验独立于增量机制,改造时不要把它和增量逻辑耦合在一起——权限归权限,同步归同步。
一个更低成本的中间态:如果今天还不值得上增量
如果任务量暂时不大,还有一个比全量拉取和增量同步都更轻的折中:先给 /todo/pull 加一个“版本探测”接口,只返回 max(updated_at) 和任务总数。客户端每次轮询先探测,版本没变就跳过拉取,变了再拉全量。它只多一个很便宜的查询,却省掉了大多数轮询的传输量;代价是一次额外的往返。这个方案适合“还没到增量复杂度,但全量传输已经开始浪费”的阶段,也是我在自己排期时会优先落地的步骤:先加探测,再改增量。
验证
需要诚实说明本文的验证边界:
- 本文的分析对象是鸿蒙开发实践中心「云同步待办清单」案例的公开设计(案例页与 GitCode 公仓 README,仓库地址:https://gitcode.com/harmony-practice-center/todo_list)。其中提到的架构事实(30 秒轮询、8 个云函数、parseBody / base64 处理方式、ON DUPLICATE KEY UPDATE 的写入方式、冷启动 504 重试等)均来自案例页原文和仓库 README,我在 2026-11-12 当天逐项阅读核对过。
- 增量改造的三条边界(软删除、只信服务端游标、毫秒精度)在写作当天通过华为云 AI DevSpace 的 AI Shell 做过一次方案讨论,会话主题为“鸿蒙待办清单增量同步改造分析”,讨论结果与案例原文能互相印证的部分我写进了上文,不能印证的部分我没有写。
- 本文没有部署和运行改造后的代码。案例页的“功能检查表”(注册与登录、创建待办、完成/取消、删除后其他设备同步变空列表等)是读者可以按步骤复核的真实清单;增量改造本身也不应只看纸面——如果读者要落地,建议先按“版本探测”这个中间态上线验证,再全量替换。
限制与风险
- 案例的安全边界要原样保留。案例页明确说明这是教学示范:APIG 认证为 NONE、会话 token 走 URL 查询参数且永不过期、端侧用 Preferences 明文存账号。增量改造不改变这一点,生产化之前应当先做认证和凭据改造,而不是先做同步优化。
- 软删除会显著放大表体积。墓碑记录需要定期清理,否则 deleted_at 的历史行会无限累积;清理周期要长于客户端可能的最大离线时间,否则长期离线的设备回连后会漏掉删除信号。
- 本文讨论的“更新”是基于字段级 updated_at 的逻辑删除与覆写。如果多个设备同时改同一条任务的同一字段,依然会出现“后写覆盖先写”的情况;这在待办清单场景下可接受,但在需要冲突合并的场景里,增量同步还需要引入向量时钟或操作日志,这超出了本文范围。
- 评测数据以外的性能结论本文不做。我没有部署和压测,所以不给出“快多少倍”之类的性能数字;表里的 O(…) 是量级上的分析,不是实测。
结论
- 全量轮询在教学和早期阶段是合理的——它用传输量换来了简单、无状态和自愈能力。不要把它当成“低级”方案。
- 增量同步值得上的信号是明确的:任务数过几百条、多设备同时轮询、流量或电量成为关注点。改造的最小切面是:
updated_at(毫秒精度)+deleted_at墓碑 +since参数 + 服务端返回的sync_token。 - 最容易出错的不是 SQL,而是三条边界:删除必须软删除、游标只信服务端时间、时间戳必须毫秒精度。漏掉任何一条,增量同步会悄悄丢数据,而且比全量模式更难发现问题。
- 如果改造排期有限,先上“版本探测”这个中间态;如果确认要全量替换增量,保留登录时拉一次全量的兜底路径。
来源:案例为鸿蒙开发实践中心「云同步待办清单:鸿蒙端云一体实战」(https://harmonybp.developer.huaweicloud.cn/cases/18f755e68ca24c298b5efb8533ef6a44),代码仓 https://gitcode.com/harmony-practice-center/todo_list。
- 点赞
- 收藏
- 关注作者
评论(0)