分片、复制、脑裂与故障检测:数据布局怎样决定恢复上限
从分片键、路由、热点、再平衡、副本延迟、quorum、epoch、脑裂与故障检测推导容量和恢复边界。
quorum、epoch 与分区路由可以组成跨实现的通用状态模型,但具体数据库仍可能采用不同的复制与故障判定协议;讨论 CAP 边界时,一致性与可用性的含义应回到 Gilbert/Lynch 论文中的形式化定义。
分片键决定数据、流量和事务能否局部化
按 tenantId、userId 或 orderId 哈希可以均匀分布,但范围查询和跨实体事务会扇出;按时间范围便于归档却让当前分片成为热点;按地域提高局部性但容量不均。好的 key 同时考虑基数、分布、访问共现、增长与合规,不能只看今天数据量。
路由映射需要版本。客户端使用旧映射把写送到迁移前分片时,目标必须重定向或拒绝,不能两边都成功。跨分片唯一性需要全局索引、预分配命名空间或业务范围内唯一,单分片唯一键不能证明全局唯一。
再平衡是在线数据迁移协议
先复制历史快照,再追增量日志,校验源目标摘要,切换路由 epoch,最后延迟清理旧副本。切换期间的双写要有权威顺序和失败恢复;简单应用层双写会在一边失败时分叉。大分片迁移会消耗磁盘、网络和缓存,必须限速并给在线流量留预算。
虚拟节点或更多逻辑分片能细化移动单位,却增加元数据和连接。热点 key 无法仅靠增加总分片解决,需要业务拆 key、局部聚合、缓存或队列串行化。再平衡完成以路由收敛、校验通过和旧 epoch 写入为零为证据。
复制既提高可用性也制造陈旧与冲突
异步副本可以分担读,但有复制 lag;读己之写需要位点 token 或回主。同步多数派要求写获得 quorum,少数侧在分区时拒绝写,以避免两个主独立推进。quorum 必须基于独立故障域,三个副本放在同一宿主机没有容灾价值。
leader term/epoch 随选举递增,目标存储和客户端拒绝旧 epoch 请求,防止旧主网络恢复后继续写。仅靠“看到对方心跳超时”宣布自己为主会在双向网络分区中产生脑裂;选主需要多数证据或外部 fencing。
故障检测永远带误判窗口
心跳超时短可以快速切换,也会把抖动和暂停误判为宕机;超时长减少误切却延长恢复。检测器给出怀疑,不给出绝对死亡证明。自动切换前要判断副本追赶、quorum、epoch 与数据损失窗口,切换后隔离旧主并审计未复制写入。
容量验收同时测正常复制、单副本追赶、再平衡和故障切换。RPO 来自同步边界与未复制尾部,RTO 包括检测、选举、路由传播、连接重建和缓存预热。只测新主端口可达,不代表数据与客户端已经收敛。
用证据边界拆开“不知道”和“做不到”
结果未知必须持久化 operationId、目标实体、预期版本和查询入口。自动重试只有在动作尚未开始或目标以相同 id 幂等时安全。用异常类名直接判断“肯定没执行”会在网络断点后制造重复提交。
状态机必须拥有终态、租约和人工出口
每个非终态都要有下一次唤醒来源:消息重投、租约到期、协调器扫描、对账任务或人工队列。只有状态而没有唤醒,流程会永久卡住;只有重试而没有终态,会永久自旋。
路由缓存是数据面里的隐藏副本
分片目录更新后,Gateway、应用客户端、连接池和批处理任务可能仍保存旧 mappingVersion。新旧分片应在迁移窗口识别版本并重定向或拒绝,客户端收到新版本后原子替换完整映射。把目录 TTL 调短只能缩小窗口,不能消除切换并发。
脑裂演练必须制造单向网络分区,而不只是停主节点。验证少数侧拒绝写、多数侧进入新 epoch、旧主恢复后先追日志再接流,并比较切换前已确认写是否全部存在。若允许数据损失,缺口必须可量化并进入对账。
用两个 Java 状态模型固定分布式不变量
下面的 Java 17 模型不模拟真实网络或共识实现,而是把本篇关键版本、租约、状态和容量关系变成确定输出。随后用真实数据库、Broker、锁服务或多进程环境注入延迟、重复、分区与崩溃。
javac --release 17 -Xlint:all -Werror examples/backend-development/distributed-systems/sharding-replication-failures/ShardRoutingDemo.java examples/backend-development/distributed-systems/sharding-replication-failures/QuorumSplitBrainDemo.java
java -cp examples/backend-development/distributed-systems/sharding-replication-failures ShardRoutingDemo
java -cp examples/backend-development/distributed-systems/sharding-replication-failures QuorumSplitBrainDemokey=tenant-42 shard=2 totalShards=4 hotShard=false mappingVersion=7
replicas=3 majority=2 sideA=2 sideB=1 writableA=true writableB=false newEpoch=12 staleEpochRejected=true输出变化时必须解释哪条一致性、epoch、幂等或容量不变量被修改。真实实验还要验证结果未知、旧所有者恢复、状态清理和人工修复路径。
容量、观测与安全要围绕协调成本
同步协调增加往返、日志刷盘、锁持有和 quorum 等待;异步收敛增加积压、版本、去重和对账存储。容量模型至少包含峰值操作率、参与者数、每次尝试、超时窗口、重试放大、状态保留、复制倍数与恢复净速率。平均值无法覆盖分区热点和长尾暂停。
反向实验要打在决定落盘的前后
故障注入必须有范围、自动停止和数据清理。生产演练从只读、单租户、单分片和低比例开始,保留旁路与回滚。完成标准不是组件重新可达,而是业务不变量保持、未知结果已对账、积压按预测下降且旧所有者无法继续写。
演进从缩小协调域开始
小规模优先单库本地事务、模块化单体和单一调度 owner。增长后先按业务冲突域分片、用批量与读模型减少跨域协调,再引入 Outbox、租约或 Saga。不要为了“分布式化”把一个本地不变量拆成跨服务事务。
任何新协调机制都写决策记录:解决什么不变量、网络分区时拒绝什么、权威证据在哪、超时后如何查询、状态保留多久、容量上限、故障演练与移除路径。系统复杂度只有在可验证收益覆盖恢复成本时才值得增加。
数据模型要为并发和恢复保留足够字段
替代方案要比较协调域而不是比较组件列表
团队门禁要阻止不可恢复的捷径
代码审查要求状态转换与 SQL 条件同时出现,集成测试要求在决定落盘前后杀进程,发布检查要求新旧版本互读,运行看板要求展示最老非终态而不只展示总数。分布式正确性不是某次设计评审的结论,而是持续被反例验证的工程属性。
