复制延迟问题
不幸的是,当应⽤程序从异步从库读取时,如果从库落后,它可能会看到过时的信息。这会导致数据库中出现明显的不⼀致:同时对主库和从库执⾏相同的查询,可能得到不同的结果,因为并⾮所有的写⼊都反映在从库中。这种不⼀致只是⼀个暂时的状态——如果停⽌写⼊数据库并等待⼀段时间,从库最终会赶上并与主库保持⼀致。出于这个原因,这种效应被称为最终⼀致性
读己之力
⽤户写⼊后从旧副本中读取数据。需要写后读(read-after-write)的⼀致性来防⽌这种异常,在这种情况下,我们需要读写⼀致性(read-after-write consistency),也称为读⼰之写⼀致性(read-your-writes consistency)
单调读
⽤户⾸先从新副本读取,然后从旧副本读取。时光倒流。为了防⽌这种异常,我们需要单调的读取。单调读取仅意味着如果⼀个⽤户顺序地进⾏多次读取,则他们不会看到时间后退,即,如果先前读取到较新的数据,后续读取不会得到更旧的数据
一致前缀读
如果某些分区的复制速度慢于其他分区,那么观察者在看到问题之前可能会看到答案。防⽌这种异常,需要另⼀种类型的保证:⼀致前缀读。如果⼀系列写⼊按某个顺序发⽣,那么任何⼈读取这些写⼊时,也会看⻅它们以同样的顺序出现。
复制延迟的解决方案
多主复制
基于领导者的复制有⼀个主要的缺点:只有⼀个主库,⽽所有的写⼊都必须通过它。如果出于任何原因(例如和主库之间的⽹络连接中断)⽆法连接到主库, 就⽆法向数据库写⼊。 基于领导者的复制模型的⾃然延伸是允许多个节点接受写⼊。 复制仍然以同样的⽅式发⽣:处理写⼊的每个节点都必须将该数据更改转发给所有其他节点。 称之为多领导者配置(也称多主、多活复制)。在这种情况下,每个领导者同时扮演其他领导者的追随者。
多主复制的应用场景
- 运维多个数据中心
- 需要离线操作的客户端
- 协同编辑
处理写入冲突
同步与异步冲突检测
原则上,可以使冲突检测同步 - 即等待写⼊被复制到所有副本,然后再告诉⽤户写⼊成功。但是,通过这样做,您将失去多主复制的主要优点:允许每个副本ᇿ⽴接受写⼊。如果您想要同步冲突检测,那么您可以使⽤单主程序复制。
避免冲突
收敛至一致的状态
- 给每个写⼊⼀个唯⼀的ID(例如,⼀个时间戳,⼀个⻓的随机数,⼀个UUID或者⼀个键和值的哈希),挑选最⾼ID的写⼊作为胜利者,并丢弃其他写⼊。如果使⽤时间戳,这种技术被称为最后写⼊胜利(LWW, last write wins)。虽然这种⽅法很流⾏,但是很容易造成数据丢失【35】。我们将在本章末尾更详细地讨论LWW。
- 为每个副本分配⼀个唯⼀的ID,ID编号更⾼的写⼊具有更⾼的优先级。这种⽅法也意味着数据丢失。
- 以某种⽅式将这些值合并在⼀起 - 例如,按字⺟顺序排序,然后连接它们(在图5-7中,合并的标题可能类似于“B/C”)。
- 在保留所有信息的显式数据结构中记录冲突,并编写解决冲突的应⽤程序代码(也许通过提示⽤户的⽅式)。
自定义冲突解决逻辑
- 写时执行
- 读时执行
什么是冲突
多主复制拓扑
无主复制
⼀些数据存储系统采⽤不同的⽅法,放弃主库的概念,并允许任何副本直接接受来⾃客户端的写⼊。最早的⼀些的复制数据系统是⽆领导的。
当节点故障时写入数据库
仲裁写⼊,法定读取,并在节点中断后读修复