Rust mmap 索引服务的一致性与崩溃恢复
常见误区 把 mmap 当作“自动持久化”会导致崩溃恢复时索引与数据文件不一致。 正确做法 数据追加写与索引写分离。 先写 WAL,再更新 mmap 索引元数据。 使用版本号和校验和做启动恢复。 恢复流程 读取 checkpoint。 回放 WAL 到最新一致点。 校验索引页并重建损坏段。 小结 mmap 解决的是访问路径效率,不是事务一致性。持久化协议仍要自己设计。
常见误区 把 mmap 当作“自动持久化”会导致崩溃恢复时索引与数据文件不一致。 正确做法 数据追加写与索引写分离。 先写 WAL,再更新 mmap 索引元数据。 使用版本号和校验和做启动恢复。 恢复流程 读取 checkpoint。 回放 WAL 到最新一致点。 校验索引页并重建损坏段。 小结 mmap 解决的是访问路径效率,不是事务一致性。持久化协议仍要自己设计。
风险根源 基于时间戳的 ID 方案在时钟回拨时可能生成重复 ID,影响去重、排序和分库分表路由。 治理策略 本地单调时钟优先,墙钟只用于校准。 发现回拨后进入保护模式:阻塞、切序列段或切机房位。 强制 NTP 漂移告警与节点摘除。 业务层补偿 关键写入加唯一约束兜底。 为 ID 冲突暴露独立错误码,支持快速定位。 小结 ID 生成器不是纯算法组件,它是时间系统的一部分。时钟治理做不好,任何高并发优化都会失效。
事故模式 下游抖动时,上游大量重试,导致下游雪上加霜,最终全链路崩溃。表面看是“下游慢”,本质是“重试风暴”。 核心思路:重试预算 对每个服务设定单位时间最大重试量。 预算耗尽后只允许失败快返,不再重试。 将预算按调用优先级分层。 参数实践 请求总超时必须小于上游超时预算。 单次重试间隔采用指数退避 + 抖动。 只对幂等请求开启自动重试。 指标面 retry_attempts_total retry_budget_remaining upstream_timeout_rate outlier_ejection_count 小结 超时、重试、熔断必须联动配置。没有预算约束的重试机制,最终一定把局部故障放大成系统性故障。
关键事实 Future 在 .await 点可能被取消。若状态更新分布在多个 await 之间,就可能出现“写了一半”的业务状态。 设计原则 把副作用集中在单一提交点。 在可取消区间只做纯计算或幂等准备。 对外部系统写入使用幂等键。 反例与修正 // 反例:先扣库存再写订单,两个 await 中间可被取消 reserve_stock().await?; create_order().await?; // 修正:准备阶段无副作用,最后一次性提交 let plan = build_plan().await?; commit(plan).await?; 工程策略 为关键流程增加“中断注入测试”。 对每个 await 标注取消后的状态语义。 引入补偿任务清理孤儿状态。 小结 异步取消是默认行为,不是异常路径。把 cancellation safety 当作接口契约的一部分,才能避免线上出现“偶发且不可复现”的脏状态。