每台机器只有「部分的真相」。复制是把同一份数据放到多台机器上,让系统在单机挂掉时还能继续服务。
为什么需要复制
单机有物理上限。数据量可能超过单机磁盘,请求量可能超过单机 CPU,还有机器故障、机房断电这些不可抗力。复制把数据拷贝到多台机器,用冗余换可用性。这是分布式系统最原始的动力------如果一台机器够用,后面的一切都不用折腾。
但复制一旦做起来,问题就跟着来了:多台机器上的同一份数据,写入该交给谁处理?

主从复制:最简单的起点
直觉做法:有一个节点说了算。指定一个主节点,所有写入都走它,从节点只负责同步数据和承接读请求。
这是最常见的方式。MySQL 的 binlog 复制、PostgreSQL 的流复制、Redis 的主从都这么做。架构简单,读可以水平扩展,写走主节点不会冲突。
问题也明显。写入瓶颈全在主节点上------从节点再多,写吞吐也卡在主节点一台机器。主节点挂了更麻烦:要切主,从节点里选一个升级。这个过程有窗口,窗口期内系统不可写。
多主复制:解写入瓶颈
主从的写入瓶颈很自然引出多主:让多个节点都能写。多主复制常见于多数据中心场景。每个数据中心有一个主节点负责本地写入,异步复制到其他数据中心。DynamoDB 的全局表、CouchDB 的同步都是这个思路。
但多个节点同时写同一份数据,冲突就来了。两个数据中心同时改了同一条记录,谁说了算?多主复制必须处理冲突------要么用最后写入者胜(LWW),靠时间戳;要么用更复杂的冲突解决逻辑(CRDT)。
无主复制:干脆不指定主
多主的冲突解决是额外复杂度。再往前走一步:干脆不指定主节点,所有节点平等,客户端直接向多个节点读写。这个思路来自 Amazon 的 Dynamo 论文。Cassandra、Riak 都采用了无主设计。客户端写数据时向多个节点发请求,读数据时也向多个节点发请求,通过 quorum 机制保证一致性。
典型配置是 W + R > N:写需要 W 个节点确认,读需要 R 个节点响应,只要 W + R 大于总节点数 N,读请求一定能看到最新的写。这个不等式很简单,但效果很好------你不需要知道哪个节点是主,只需要数确认数。
无主的好处是节点平等,没有切主问题,故障时自然降级。代价是实现复杂:冲突得在读路径上解决,读修复、反熵这些机制要补上。
复制延迟
不管选哪种复制,从节点追上主节点需要时间。这个延迟带来一类读一致性问题。
你刚写了数据,读到从节点发现数据没了------读己之写。你连续读了两次,第二次发现数据退回去了------单调读失败。这些问题不只在主从中出现,多主和无主一样要面对,只要存在异步复制就有这个窗口。
同步还是异步
同步复制等所有从节点确认再返回,强一致但慢;异步复制主节点写完就返回,快但可能丢数据。主从、多主、无主都有这个取舍,只是具体实现方式不同。这是分布式系统里最经典的权衡:没选同步,就要接受丢数据的可能;选了同步,就要接受延迟。
故障恢复
节点挂了再加回来,怎么追上错过的数据。主从里靠重放日志,无主里靠读修复和 hinted handoff。每种模式的恢复机制不同,但问题是共通的。
小结
复制是让数据在空间上分布的手段。主从最简单,但写入瓶颈和切主是代价;多主解了写入瓶颈,但引入冲突解决;无主最灵活,节点平等、故障自然降级,但实现复杂度最高。三个方案不是互斥的------实际系统里经常混着用,比如主从架构配 quorum 读写。
无论选哪种,复制延迟和一致性的取舍是绕不开的。这是分布式系统在空间维度上的核心矛盾:想用复制换可用性,就得接受数据不会立刻一致。