一次分布式存储写入通常经历哪些步骤?
简化版
写请求通常经过鉴权限流、分片路由与 epoch 校验、Leader/协调者排序、WAL 持久化、副本复制达到确认条件、提交并应用本地引擎,最后返回版本或提交水位。客户端超时不等于失败,重试必须带幂等 ID;“成功”还要明确是主节点落盘、远端落盘、多数提交还是状态机已应用。
详细版
网关先校验租户、配额和请求大小,再按 key 找到 shard。旧路由请求应收到 STALE_EPOCH 并刷新,旧 owner 不能继续接受写。
Leader 为命令分配 term/index 或协调者生成版本,追加 WAL 后复制到副本。三副本 Raft 通常在当前任期日志被 2 个副本持久化后推进提交,不必等待第三个。
提交与应用不同:commitIndex 已形成共识,appliedIndex 表示本地引擎已执行。是否在 apply 后才回复取决于 API 返回内容,但后续线性读必须等到相应应用水位。
若客户端 100 ms 超时,而服务器在 110 ms 提交,直接用新请求号重试可能重复扣款。应使用 clientId + requestSeq 或业务唯一键去重,并提供状态查询。
完整版教学
一、入口保护与路由
写入先做认证、授权、配额、大小限制和 deadline 校验,防止无效请求进入昂贵复制路径。路由元数据把 key 定位到 shard,客户端携带 epoch,服务端拒绝过期所有权。
client -> gateway -> shard leader -> WAL/log -> replicas -> commit -> apply -> ACK
二、排序与并发控制
Leader 模式由单一 Leader 给日志排序;无主系统由协调者向多个副本写版本,并在冲突时合并。条件写还要携带 expectedVersion,服务端用 CAS 防丢失更新。
| 写类型 | 关键条件 | 失败语义 |
|---|---|---|
| 普通 Put | 幂等请求号 | 超时结果未知 |
| CAS | expectedVersion | 冲突需重读 |
| 事务写 | 事务/锁时间戳 | 可能需协调恢复 |
| 删除 | tombstone/version | 防旧值复活 |
三、WAL 与持久化边界
WAL 先顺序追加,让崩溃后可重放。写入 page cache 不等于断电持久,是否 fsync、组提交周期和设备写缓存策略共同决定 durable 语义。批量 fsync 可提高吞吐,却会增加等待和暴露窗口。
例如每 2 ms 组提交一次,100 个请求可共享一次 fsync;平均延迟增加约一个批次等待,但 IOPS 大幅降低。数字需结合设备 P99 压测,不能固定照搬。
四、副本复制与成功确认
三副本中 Leader 和一个 Follower 持久化即可形成多数,但 Raft 还要求当前任期提交规则。异步复制可能只等主节点;半同步可能等一个从库。API 文档必须明确 ACK 集合和故障后的 RPO。
少数派不可达时,共识写应失败或等待,而不是由旧 Leader 本地成功。超时发生在 ACK 前,写可能未发生、进行中或已提交,客户端必须按未知结果处理。
五、提交、应用与返回
共识提交后,各副本按日志顺序应用到 LSM/B+Tree。若命令包含外部副作用,apply 重放必须幂等;更好的做法是状态机只产生确定性本地状态,再由 Outbox 驱动外部动作。
返回最新对象时通常需等待本地 apply;只返回“已接受”可以更早,但 API 必须显式表示异步状态。不能用一个模糊的 success 同时代表多种完成层级。
六、超时、重试与背压
客户端携带稳定请求 ID,Leader 保存去重表或把业务唯一键写入状态机。重复请求返回原结果,而不是再次执行。去重记录的保留期必须覆盖最大重试窗口。
系统在 WAL 队列、复制窗口、apply backlog 或磁盘水位超标时应背压,拒绝比无限排队更可控。监控分解 route、queue、fsync、replication、commit、apply 各段延迟,以及 unknown-result、重试、去重命中和 stall。
记忆钩子:写路径的核心问题是“谁排序、写到哪里算成功、超时后如何不重复”,而不是只画一条主从复制箭头。
七、常见误区与追问
- 误区:客户端超时就代表写失败。 请求可能已经提交,只是响应丢失。
- 误区:WAL 写成功就代表多副本安全。 还要看远端确认和提交规则。
- 误区:commitIndex 等于数据已可查询。 本地状态机可能尚未应用到该水位。
- 追问:重试如何防重复扣款? 稳定请求 ID、业务唯一约束和状态机去重共同保护。
- 追问:为什么要拒绝旧 epoch 写? 防止迁移后旧 owner 继续形成分叉。
- 追问:写入慢如何定位? 分段看排队、fsync、复制、commit 和 apply,而非只看总延迟。
八、加强记忆
把写路径记成“校验路由、排序落 WAL、复制达门槛、提交再应用、携水位返回”。epoch 防写错分片,quorum 防已确认历史丢失,幂等 ID 处理未知结果,背压保护故障时资源。答清 ACK 的精确层级,才算讲清写入语义。