分布式系统中的高一致性通常要用可用性或延迟来支付成本
做订单、支付、库存的人,大多都见过这种现场:支付回调已经返回成功,订单服务也记成了“已支付”,可紧接着另一个服务查出来的还是“待支付”。再糟一点,库存已经扣了,前台却还在卖。
很多团队碰到这种问题,第一反应是调参数:把“写成功”从 1 个副本确认改成 2 个、3 个,觉得这样系统就强一致了。
这招有用,但不够。副本确认数只是问题的一部分。它能提高“这次写入别轻易丢”的把握,却不能单独决定一致性。因为用户最后感受到的,不是某个副本有没有收到数据,而是整个系统怎么写、怎么读、出故障时怎么收场。
拿最常见的主从复制来说。订单状态先写到 leader,leader 再把日志复制给 followers。如果写入时等到了多数副本确认,说明这条数据相对安全了。但如果下一秒读请求打到了一个还没追上的 follower,用户还是会读到旧值。写入“看起来成功了”,读取“看起来也正常”,可结果还是不一致。
再往前走一步,问题会更明显。假设 leader 刚确认完一笔写入就宕机了,新 leader 选出来以后,如果日志恢复、选主规则、提交点处理得不严,这笔“已经成功”的写入,理论上仍然可能丢。你会发现,问题从来不只是“等了几个副本确认”,而是整条读写链路是不是闭环。
所以,真正决定一致性的,通常不是一个参数,而是一整套协议设计:写入什么时候才算提交,读取到底从哪里读,系统有没有明确的全局顺序,故障恢复时认不认之前确认过的写入。少了其中任何一环,所谓“强一致”都可能只是局部看起来很强。
代价也正好藏在这些环节里。
系统正常运行时,成本先体现在延迟上。本来一次本地落盘就能返回,现在要等多台机器复制、确认,有时还要走 leader 或 quorum 才敢给你最新值。请求路径变长了,热点写入更容易卡住,跨机房时感受会更明显。系统没坏,但它就是会慢一些。
真到故障发生时,成本又会换个地方出现。网络一分区,或者节点数不够,系统如果还继续随意接收写入,后面就可能出现两个版本都自称自己是“真相”。为了避免这种事,它只能拒绝部分请求,甚至暂停写入。业务会觉得“服务明明活着,怎么不能用了”,但这恰恰是系统在拿可用性换一致性。不是它脆弱,而是它在守底线。
这也是为什么“要不要强一致”从来不是一个开关,而是一个工程判断。
如果你做的是账户余额、支付状态、库存扣减,系统更怕的是错,不是慢。那就应该把读写路径收紧,让提交、读取、切主恢复都围绕“不能读错、不能回滚已确认结果”来设计。你要接受的代价,就是更高的延迟、更低的吞吐,以及异常情况下更保守的可用性。
如果你做的是推荐、埋点、消息流、运营看板,短时间读到旧值未必是事故,靠异步修正和补偿也能兜住,那就没必要把每一次读写都做成“高规格同步决策”。这时候,放宽一致性,换吞吐、换响应速度,往往更划算。
很多人熟悉 CAP,但日常做架构决策时,更贴近现实的其实是 PACELC。它补上的那半句话很重要:网络分区发生时,你要在一致性和可用性之间选;网络分区没发生时,你还是要在一致性和延迟之间选。也就是说,强一致的成本不是只在故障时才出现。系统平稳运行时,账单其实就已经在累积了。
所以,别再只问“写入确认从 1 提到 3,算不算强一致”。更应该问的是:我的读从哪里来?写入什么时候才算真正提交?切主以后,之前确认过的数据还认不认?业务更怕短暂不可用,还是更怕读到错误结果?
系统里没有免费的强一致。你不在延迟上付,就得在可用性上付;如果这两边都不想付,最后多半会在补偿逻辑和业务复杂度上补票。