“CAP 三选二”大概是分布式系统里被引用最多、误解也最多的一句话。它说的是:在网络分区发生时,一致性和可用性只能保一个。注意前提是分区发生时。
绝大多数时间网络是通的,这时候的取舍是另一个问题。
一、PACELC:把“平时”也纳入考虑
PACELC 是对 CAP 的补充:
- 如果发生分区(P),在可用性(A)和一致性(C)之间选
- 否则(E,else),在延迟(L)和一致性(C)之间选
这更贴近日常:没有分区时,你依然要在“多副本同步写(慢但一致)”和“异步复制(快但可能读到旧值)”之间做选择。
二、取舍一:读旧数据能不能接受
以博客的评论数为例:
- 强一致:每次评论都同步更新计数,读到的数字永远准确,但写变慢
- 最终一致:先写评论,计数异步累加,短时间内可能少一两条
对内容站,后者几乎总是更好的选择。用户不会因为评论数晚两秒更新而离开,但会因为发布失败而离开。
判断标准是:这个数字错了,会不会导致错误决策或资金损失? 不会,就允许旧。
三、取舍二:写操作要不要幂等
网络不可靠,超时是常态。客户端超时后会重试,而服务端可能已经执行成功。
如果接口不幂等,重试就会产生重复数据:重复评论、重复下单、重复扣款。
实现幂等的常见方式:
- 客户端生成请求 ID,服务端去重表
- 业务唯一键(同一用户对同一文章只能点一次赞)
- 状态机 + 版本号,拒绝过期写入
四、取舍三:失败之后怎么补偿
分布式写往往跨多个存储:正文、索引、计数、搜索。任何一步都可能失败。
三种策略:
- 尽力而为:失败就记日志,人工修
- 重试 + 死信队列:自动重试,超过次数转人工
- 对账与重建:定期扫全量,重建派生数据
个人项目通常没资源做完整对账,那就至少做到“派生数据可重建”。索引坏了能从正文重建,计数错了能从明细重算——这比追求“永不失败”现实得多。
五、一个具体例子:索引的读改写
很多系统用“读索引 → 修改 → 写回”的方式维护列表。并发写时会互相覆盖。
可选方案:
- 加锁(边缘环境通常没有)
- 用支持原子追加的存储
- 接受偶尔丢失,并提供重建
- 把写收敛到单点(单区域、单实例)
没有哪个是免费的。关键是明确你选了哪个,并让系统在出错时可恢复。
结语
CAP 告诉你极端情况下必须放弃什么;PACELC 提醒你平时也在做取舍。真正让系统可靠的,往往不是选对了某个定理,而是幂等、重试、可重建这三件朴素的事。
评论 (0)