如何配置“retry”策略使“_bulk_docs”幂等?
解读
在国内互联网、金融、物联网等真实业务中,_bulk_docs 被大量用于离线端批量同步、日志归集、订单对账等场景。
由于移动网络抖动、网关超时、容器重启等原因,客户端经常触发“自动重试”。
如果重试策略配置不当,同一条批量请求可能被 CouchDB 执行多次,导致:
- 同一文档生成多个冲突版本(_conflicts 数组膨胀);
- 业务层计数、库存、金额被重复累加;
- 移动端同步 checkpoint 错乱,引发“幽灵数据”。
因此,面试官真正想考察的是:
“在保留 CouchDB 原生多版本机制的前提下,如何通过客户端重试策略与文档设计,让 _bulk_docs 在业务语义上只生效一次”。
知识点
-
_bulk_docs 的语义
- 默认 all_or_nothing=false,即逐条写入;任何一条失败不影响其余文档。
- 当携带 "new_edits": false 时,CouchDB 会绕过版本检查,直接存储指定修订,具备天然幂等性,但要求客户端必须提前拿到 正确的 _rev。
-
幂等键(Idempotency-Key)模式
- 在每条文档体内加入 biz_id + 请求批次号 组成的复合键,写入前用 _find 或 _view 做存在性校验。
- 利用 _design/update 函数 在服务器端做“upsert 去重”,把重复请求转空操作。
-
重试策略配置要点
- 指数退避 + 最大重试次数(国内公有云环境建议 3~5 次,退避基数 500 ms)。
- 只重试 5xx 与 429;对 4xx(除 429)直接失败,避免把业务错误放大。
- 同一批次内文档顺序固定,防止并发场景下“部分成功”后重试顺序错乱。
-
移动/离线场景补充
- 在 PouchDB 端开启 retry: true, live: true 同步时,自定义 filter 函数 把已确认写入的 biz_id 排除掉,实现“客户端幂等过滤”。
- 对账阶段用 _changes?since=last_seq 拉取增量,按 biz_id 去重合并,确保最终一致。
答案
线上环境推荐“new_edits=false + 精确 _rev”与“幂等键 + 服务端 update 函数”双轨方案:
-
能提前拿到 _rev 的同步链路(如主主复制、边缘节点回源)
- 客户端在本地维护 rev_map(id→rev)。
- 构造 _bulk_docs 时置 "new_edits": false,并填入准确 _rev;
- 重试策略:
- 超时或 5xx/429 时按 2^n × 500 ms 退避,最多 3 次;
- 收到 201 响应后,立即更新本地 rev_map,后续重试使用新 _rev,保证整条链路幂等。
-
无法提前拿到 _rev 的业务写入(如 APP 端首次上报订单)
- 每条文档携带 biz_id + 请求批次号 作为业务主键;
- 设计 /_design/dedup 的 update 函数:
function(doc, req) { var key = req.body.biz_id + req.body.batch_no; if (doc && doc.processed_keys && doc.processed_keys.indexOf(key) >= 0) { return [null, "ignored"]; } if (!doc) doc = {_id: req.id}; doc.processed_keys = doc.processed_keys || []; doc.processed_keys.push(key); // 继续合并业务字段 return [doc, "updated"]; } - 客户端重试策略:
- 仍采用 指数退避 3 次;
- 每次重试保持 biz_id 与 batch_no 不变;
- 收到 201 后把该批次号标记为“已落库”,本地不再重发。
通过以上配置,_bulk_docs 在网络抖动、网关重发、容器重启等典型国内云场景下,业务层面只会生效一次,满足面试官对“幂等重试”的严苛要求。
拓展思考
-
跨地域多活场景
若北京、上海双集群同时接受写入,new_edits=false 方案需配合 全局序列号服务(如基于 Redis Lua 的 INCR + 时间戳)预分配 _rev 号段,避免集群间 rev 碰撞导致冲突爆炸。 -
大数据量一次性迁移
对历史库做 tb 级 _bulk_docs 导入时,可先把 target 集群设置 q=1、n=1,关闭 delayed_commits,批量写入完成后再 reshard 并调高副本数,既保证幂等又降低磁盘 IO 抖动。 -
审计与排查
在文档内增加 __retry_cnt 字段,每次重试时自增;结合 ELK 收集 429/502 日志,按 __retry_cnt 聚合可快速定位国内某运营商网络异常导致的重试风暴,实现 可观测幂等。