《数据密集型应用系统设计》章节总结 第五章 数据复制

目的:低延迟、高可用、高容量

主要方式:主从复制、多主节点复制、无主节点复制

复制策略:同步复制、异步复制

主节点与从节点

主从复制工作原理:

  1. 选定某个副本为主节点,写入工作通过主节点进行
  2. 主节点写入数据后,将数据的更改发送给从节点
  3. 读取数据可以从主节点活从节点进行

同步复制与异步复制

全同步:主节点复制需要等待全部从节点完成并反馈

半同步:主节点复制只需等待一个从节点完成并反馈

全异步:主节点发送复制消息后,无需等待从节点复制完成的反馈

配置新的从节点

建立新的从节点的过程:

  1. 生成快照
  2. 基于快照的全量复制
  3. 从节点连接主节点获取快照后所有修改的日志
  4. 根据日志进行增量复制

处理节点失效

主节点和从节点都存在失效的可能性,因此应当采取手段尽量保持系统的正常运行。

从节点失效:追赶式恢复

从节点失效后,重新获取在失效后主节点更新的日志,依据日志进行恢复

主节点失效:节点切换

需要选取某个从节点为主节点,通常具有三个步骤:

  1. 确定主节点失效:通常通过心跳包的响应时间判断
  2. 选举主节点:指定一个所有节点统一的主节点,该节点最好与主节点数据差异较小
  3. 重新配置主节点:需要令客户端将请求发送至新节点

但是主节点的切换存在多种可能的问题:

  • 异步数据丢失
  • 脑裂
  • 心跳响应时间阈值设定

复制日志的实现

基于语句的复制

主从复制过程中直接复制主节点执行的逻辑语句,但是这存在一些问题,比如在语句中应用了时间获取、随机数生成等操作。

MySQL5.1之前的版本采用基于语句的复制方式。

基于预写日志(WAL)传输

对于负责向磁盘写入内容的存储引擎,都会在写入磁盘之前向WAL中追加将要写入的字节序列,WAL可以理解为一种物理日志,用来记录对于磁盘的修改,但是仍然有一些问题,WAL与存储引擎强相关,该方式可能无法适应存储格式的改变,不能兼容运行于主从节点不同的引擎。

Oracle等系统支持该方式。

基于行的逻辑日志复制

逻辑日志是用来记录行的增删改行为的记录,例如MySQL中的二进制日志binlog,逻辑日志与存储引擎解耦,可以通过复制逻辑日志实现主从复制。

MySQL当今应用这种方式,还要配合relay log。

基于触发器的复制

触发器支持用户注册自己的应用代码,使得数据库执行数据更改时自动执行该代码。通过该技术可以将数据更改记录同步到另一个表,外部处理逻辑访问该表从而实施必要的逻辑。

复制滞后问题

只要不采用全同步,就会产生各个副本数据不一样的问题,也就是一致性问题

读自己的写

用户A向数据库执行某更改操作后,后续再去读取却看不到刚刚的更改操作,这种情况违反了”读写一致性“

可能由于从节点未及时复制主节点,且读操作由从节点执行导致。

可能但不一定好使的解决方案:

  • 如果访问可能被修改的数据,从主节点读取
  • 更新后一段时间内保持从主节点读取,但是延迟时间不一定好确定
  • 客户端请求附带时间戳,但是可能会有时钟不可靠问题

另外还有跨设备的读写一致性问题

单调读

用户A对于数据库先后执行两次读取,后一次的数据比前一次的更旧,这种情况违反了”单调读一致性“

可能由于不同从节点复制完成时间不一致,且先访问了已经复制完成的节点,后访问未完成的节点导致。

可能的解决方案:同一用户每次从同一个从节点读取数据

前缀一致读

用户A观察用户B具有因果关系的两次更改操作,但是却观察到”果“发生于“因”之前,这种情况违反了“前缀读一致性”

可能的解决方案:将具有因果关系的操作交由同一个从节点完成

复制滞后的解决方案

解决复制滞后属于分布式事务的范畴,分布式事务对于性能可能造成极大的影响,这个问题放在后续章节详细讨论。

多主节点复制

主从复制将写入操作放在同一个节点,多主节点复制中有多个节点可以接受写入操作,并将改写入操作同步到其他主节点,各个主节点之间保持数据尽量同步。

使用场景

跨不同的数据中心进行多主节点复制较为合理。

多数据中心

在部署于多个数据中心时,主从复制与多主节点复制的区别:

  • 性能:主从复制无法实现就近复制
  • 容忍数据中心失效:多主节点复制
  • 容忍网络故障:多主节点采用异步复制,能够更好的解决该问题

以上是多主节点复制的优势,然而具有一个极大的劣势,不同的数据中心修改相同的数据,即写冲突

离线客户端操作

如果用户手机、电脑中的日历等记录需要和云服务器进行同步,那么用户设备和服务器可分别视为两个主数据库。

协作编辑

类似腾讯文档,各个用户都相当于一个主节点,但是需要用锁解决并发冲突。

处理写冲突

如何解决在两个数据中心中对于相同数据执行不同修改的问题呢?

同步与异步冲突检测

同步冲突检测:在对主节点A进行修改时,同步等待其他节点修改完成,然而这样相当于回到了主从复制。

避免冲突

确保同一用户的更改请求由固定的主节点执行。不过也会产生一些问题,比如用户坐飞机出差会导致对应主节点的 切换。

收敛于一致状态

如果各个主节点存在具有冲突的记录,该保留那个,可能的方式有:

  • 根据时间相关的UUID
  • 根据各个副本的ID
  • 将结果组合
  • 设定预定的方案

自定义冲突解决逻辑

解决冲突更合适的方式是依靠其应用者,此时有两大类方式:

  • 写入时执行
  • 读取时执行

什么是冲突

事务的隔离性,幻读等等问题,不必在此详述。

拓扑结构

拓扑结构:写请求从一个节点路由到其他节点的路径,典型结构包括:环形,星形,全部-全部

无主节点复制

无主节点复制模式中,不存在主节点与从节点之分,所有节点都可以接受写入请求,也就是全都是主节点,该模式的两种主要实现方式:

  • 分别发送多个副本
  • 协调者代为写入

节点失效时写入数据库

可能存在部分节点失效,而客户端只需获得一定数量节点的写入请求,无需全部节点的请求。

崩溃节点重新上线可能会使得系统中保存过期的数据,此时不必立即同步,因为客户端读取数据时向所有节点发送请求,客户端只需从得到的相应中选取最新的就可以。

读修复与反熵

崩溃节点重新上线后如何修复那些旧数据?Dynamo风格数据库两种解决思路:

  • 读修复:在用户读取数据时发现某个版本数据版本过期时,客户端将新值写入存储过期值的副本,懒汉式
  • 反熵过程:后台进程定期寻找各个副本之间的差异,并进行复制,饿汉式

读写quroum

由于节点可能出现故障,因此每次的写入无法保证在所有节点都成功,那么应该规定一个阈值,只要写入成功的节点数大于等于该阈值就可以视为写入成功。同样的原因和道理,读取操作也应该规定这么一个阈值。

写阈值w,读阈值r,节点数目n,他们应该满足w+r>n,该方案称为仲裁写/读

Quorum一致性的局限

如果希望提高系统的可用性,那可以将w+r>n的条件放松至w+r>=n,此时可用性提高,但是更容易出现读取数据的一致性问题。

即便是在w+r>n的条件下,也会存在一些问题:

  • 采用sloppy quorum使得写入和读取的节点可能完全不重复
  • 同时写入的冲突问题
  • 写操作异步导致读取新值还是旧值不确定
  • 如果未能够成功写入超过w个节点,但是不能回滚
  • 即便一切正常也会由于异步产生的单调读问题

监控旧值

对于主从复制的方式,可以很容易的通过日志判定从节点相对于主节点落后的程度。

对于无主节点复制系统,由于每个节点都接收写入,导致判定较为困难。

宽松的quorum与数据回传

如果客户端写入/读取能够连接到的节点无法满足w/r的需求,那么有以下方式应对:

  • 如果无法满足要求,是否要将错误明确返回
  • 接受这些请求,并将他们暂时存储在一些可以访问的其他节点,这些节点不属于原有的n个节点,该方法称为放松的仲裁(sloppy quorum)

sloppy能很好提升系统的可用性。

多数据中心操作

如何令响应时间更小

检测并发写

对于多主节点复制,即便采用严格的quorum机制,由于异步的复制操作,也会引起一致性问题,因此需要一些解决该问题的方案。

最后写入者获胜(丢弃并发写)

最后写入者获胜(Last Write Win),通过时间戳确定,但是该方法会牺牲数据的持久性能。

happens-before关系和并发

并发:在先后顺序上存在依赖关系的两个步骤存在并发

并发的关键不在于时间上的同时,而在于逻辑上的依赖,在于两个操作是否需要意识到对方的存在。

确定前后关系

介绍了一种确定操作并发性的方法。

合并同时写入的值

如何将并发操作合并,涉及到了如何进行删除操作

版本矢量

版本矢量:各个节点版本号的集合

小结

  • 复制的目的
  • 复制的方案
    • 主从复制
    • 多主节点复制
    • 无主节点复制
  • 一致性问题
    • 读写一致性
    • 单调读一致性
    • 前缀读一致性

你可能感兴趣的:(《数据密集型应用系统设计》章节总结 第五章 数据复制)