本节摘要:writeConcern 决定写入到几个节点才算确认,readPreference 决定读路由到哪个节点;两者配置不当会导致"确认了却丢了"与"读到了旧数据"。本节从一次升级瞬间的丢单事故讲这两组参数与回滚机制。
平台默认 writeConcern 是 {w: 1}(主节点确认即返回)。运维滚动重启,旧主在确认 300 笔订单后、尚未同步给从节点时断电。选举产生新主,旧主恢复后发现这 300 笔写不在新主的时间线里,被整体回滚进 rollback 目录的 BSON 文件。用户收到了支付成功,订单系统却查无此单。
// 关键业务必须多数派确认 db.orders.insertOne(order, { writeConcern: { w: "majority" } }); // 更强的兜底:多数派 + 落盘日志确认 { writeConcern: { w: "majority", j: true } }
w:1 只是"主收到了",主一掉这些写就可能回滚;w:"majority" 表示多数节点都已复制,配合多数派选举原则,被回滚的窗口在协议层被消除。
| 取值 | 路由 | 风险 |
|---|---|---|
| primary | 永远读主 | 无一致性问题,主压力大 |
| primaryPreferred | 主优先,失联读从 | 故障转移期读到旧数据 |
| secondary | 只读从 | 读扩展,可能明显滞后 |
| secondaryPreferred | 从优先 | 同上 |
| nearest | 延迟最低者 | 与新旧无关 |
// 读自己的写:会话级因果一致性 const session = db.getMongo().startSession({ causalConsistency: true }); // 报表实例:读从库、允许过期 60 秒 db.getMongo().setReadPref("secondaryPreferred");
从节点读的价值是分流报表与统计这类"旧一点无所谓"的读;订单详情页这种"付款完立刻看"的场景读从库,就是在制造客诉。
💡 回滚数据不会消失,落在数据目录的 rollback 文件夹里,可人工导回。但"可以救"不等于"不算事故"——把 writeConcern 写进关键集合的规范,比事后捞文件体面得多。
丢单确认后,恢复动作分四步。第一步锁住支付回调的重放入口,防止补偿逻辑与人工导回双写;第二步从旧主的 rollback 目录取出 BSON 文件,逐条核对(文件里每条记录带着原始操作与文档快照);第三步用脚本把 300 笔订单重新插入新主,这次全部带 w:"majority";第四步与支付渠道对账,确认无重复入账。全程六小时,其中四小时花在核对上。事后整改分三层:订单与支付集合的驱动连接默认 writeConcern 改为 majority(代价是每次写多一次跨节点往返,压测显示写入延迟从 3 毫秒升到 9 毫秒,业务完全可接受);开启 causallyConsistent 会话覆盖"支付完查订单"链路;把 w:1 的默认值在新人培训里列为"只存在于测试环境"。
writeConcern 的选型其实是一张风险价目表,值得按业务逐类定,而不是一刀切:
| 写入类型 | 建议 writeConcern | 理由 |
|---|---|---|
| 订单、支付流水 | w: "majority", j: true | 回滚窗口清零,落盘确认 |
| 用户资料、配置 | w: "majority" | 丢失代价高但可容忍毫秒延迟 |
| 行为日志、埋点 | w: 1 | 丢一条无感,吞吐优先 |
| 分析类批量导入 | w: 1 + ordered:false | 源头可重放 |
readPreference 的配套原则是"读写关注成对设计":写用 majority 而读用 secondary,等于花了两倍的确认成本又主动去读旧数据,钱花了效果抵消了。一个常见的正确组合是"写 majority + 读 primary"给交易链路,"写 w:1 + 读 secondary"给日志分析链路,两条链路各得其所。
// 从 rollback 目录人工导回的示意 mongodump --host 旧主 --db local --collection 里没有现成集合, // 正确做法:rollback 文件是 BSON,按操作类型重放 // 每个文件形如 rollback.10086.bson,含被回滚的完整文档 // 核对 orderId 后逐条 insertOne 回新主,带 majority
两者常被混为一谈。w:"majority" 回答"几个节点收到了",j:true 回答"收到后是否已写进 journal 文件"。majority 不带 j,在节点收到但尚未刷盘的窗口里断电,仍可能丢——不过该窗口极短(journal 默认 100 毫秒提交一次)。最强的组合是 majority 加 j,性能最敏感的场景可以只 majority。另外 5.0 起默认 writeConcern 已在向 majority 演进,新版本集群的默认行为比旧文档描述的更安全,升级后值得重新确认应用里是否还残留手写的 w:1。
回滚事故的完整处置流程值得按顺序记住:第一步确认哪些节点被回滚(连上每个从库看它的 optime 与主库的差距);第二步从回滚目录(默认 rollback/ 下)导出被回滚文档;第三步核对业务主键(如 orderId)确认哪些是有效写入;第四步用带 majority 写关注的语句回写新主;最后一步复查应用日志确认没有新的回滚发生。整套流程最怕的是"发现回滚后手忙脚乱地往老主库里插数据",那会把两个主库的历史搅得更乱。