数据密集型应用系统设计-数据复制

date
Dec 10, 2021
slug
reading-notes-ddia-replication
status
Published
tags
读书
系统和网络
summary
type
Page
复制的目的:
  • 使数据在地理位置上更接近用户,从而降低延迟
  • 当部分组件出现故障,系统依旧可以继续工作,从而提高可用性
  • 扩展至多机以同时提供数据访问服务,从而提高读吞吐量
目前有三种主流复制数据变化的方法:主从复制多主节点复制无主节点复制
 

主节点与从节点

同步、半同步、异步复制
  • 同步复制:一旦向用户确认,从节点可以明确保证完成了主节点的更新同步,数据已经是最新版本。万一主节点故障,总是可以在从节点访问最新数据。但缺点是如果同步的从节点无法完成确认,如由于网络故障,写入就不能视为成功,主节点会阻塞其后所有的写操作。
  • 半同步复制:从节点中只有一个节点是同步复制的,其他从节点则是异步复制。万一同步的从节点变得不可用或性能下降,则将另一个异步从节点提升为同步复制,这样可以保证至少有两个节点(主节点和有一个同步节点)有最新数据。
  • 全异步复制:所有从节点都是异步复制,如果主节点发生失败且不可恢复,则尚未复制到从节点的写请求都会丢失,这意味着向用户确认了写操作,却无法保证数据的持久化,不过优点是主节点可以不需等待从节点复制完成,无视从节点复制滞后问题,系统吞吐性能组好。
 
配置新的从节点操作步骤如下:
  • 在某个时间点对主节点数据副本产生一个一致性快照,避免长时间锁定数据库。
  • 将此快照拷贝到新的从节点。
  • 从节点连接到主节点并请求快照之后的所有数据更改日志,因为在第一步创建快照时快照与系统复制日志的某个确定位置相关联,这个位置信息在不同的系统有不同称呼,PostgreSQL 叫做 log sequence number,MySQL 叫做 binlog coordinates。
  • 从节点应用快照之后的数据变更,称之为 追赶。之后继续处理主节点的变化。
 
处理失效节点
  • 从节点失效 - 追赶式恢复:根据副本的夫之日止,从节点可以知道在发生故障之前所粗粒的最后一笔事务,然后连接到主节点,请求自那笔事务会后中断期间的所以有数据变更。收到这些数据变更日志后,应用在本地来追赶主节点。
  • 主节点失效 - 节点切换:选择某个从节点将其提升为主节点,客户端也需要更新,这一样之后的请求才会分发给新的主节点,其他从接待您要接受来自新的主节点的数据变更,这称之为切换
 
主节点切换的步骤
  • 确认主节点失效:一般基于超时机制,节点间频繁地互相发送心跳存活消息,如果发现一个节点长时间没有响应,则认为该节点发生失效。
  • 选举新的主节点:通过共识机制来在从节点中选举出新的主节点。
  • 重新配置系统使新主节点生效:客户端请求发送给新主节点,从节点接收新主节点更新。旧主节点上线后需要系统确保其降级为从节点。
 
主节点切换过程中可能存在的问题:
  • 异步复制模式下原主节点有数据为完全同步给其他从节点,选举之后原主节点重新上线后,新从节点可能会收到冲突的写请求,因为原主节点未意识到角色变化,还在尝试同步其他从节点。常见的解决方案是丢弃掉原主节点上未完成复制的写请求。但这违背了数据持久化的承诺。如果整个系统还在数据库之外有别的软件协同工作就会出现数据不一致的问题,如 Redis 中缓存数据。
  • 在某些故障情况下会出现两个节点同时自认为是主节点,即脑裂,两个主节点都可能接收写请求,并且没有很好办法解决冲突,最后数据可能丢失或破坏,作为一种应急方案,有些系统会强制关闭其中一个。
  • 超时检测时间过长意味着整体恢复时间变长,如果设置过段则可能会出现很多不必要的切换。如突发负载峰值导致节点响应时间变长或超时,此时触发切换会导致整体情况变得更糟。
 
复制日志的实现
  • 基于语句的复制:主节点记录所执行的每个写请求(操作语句)并将该操作语句作为日志发送给从节点。对于一些非确定性函数会产生不适用的场景。
  • 基于预写日志 WAL 传输:日志结构存储引擎如 SSTables 和 LSM-Tree,或者 B-tree 的预先写入日志两种方式都是将数据库写入的字节序列记录到日志中,因此可以是要用完全相同的日志在另一个节点上构建副本,主节点除了在日志写入磁盘外,还通过网络传输给其他从节点。PostgreSQL 和 Oracle 支持这种复制。WAL 描述的数据格式非常底层, 其包含了哪些磁盘块的哪些字节发生变化,使得复制方案和存储引擎紧密结合,这要求同步两端要相互兼容。
  • 基于行的逻辑日志复制:基于行的复制是指日志中保存改动的行的原值和新值,由于逻辑日志与存储引擎解耦,因此可以保持向后兼容。同时对于外部应用来说逻辑日志也容易解析,将数据内容发送给其他系统可以构建自定义缓存等,成为变更数据捕获(change data capture)。
  • 基于触发器的日志:。。。
 

复制滞后问题

增加从副本可以提高系统的吞吐量,但这种方式只能用于异步复制,如果试图同步复制所有从副本则单个节点故障或网络终端将使整个系统无法写入。节点越多故障率越高,所以完全同步的配置现实中反而非常不可靠。
但异步复制中会存在复制滞后问题,即从副本落后于主副本。
 
读自己的写、写后读一致性、读写一致性 read-after-write consistency
notion image
保证用户写入后总能读取到自己最新提交的数据,解决的是主从之间的复制延迟问题
  • 如果访问可能被修改的内容则从主节点读取,否则读从节点。如个人主页只能自己编辑而其他人无法编辑。
  • 如果应用大部分都能被修改则上述方法不适用,可以跟踪最近更新时间,如果更新后一分钟内则读主节点,否则读从节点。更新时间也可以由客户端自动携带。
  • 如果有多个数据中心则更复杂,需要先把请求路由到主节点所在的数据中心。
  • 如果用户在多端登录还要考虑配置全局元数据保证多端看到同样的配置,同时还要将多端的网络请求路由到同一个数据中心上。
 
单调读
notion image
如果在一主多从的结构下,用户写入后接着读取两次,第一次读取到最新内容,第二次读取到过期内容,即出现刚刚的数据消失了的现象。
单调读比强一致性弱,比最终一致性强,可以保证用户总是从同一副本执行读取,如基于用户ID的哈希而不是随机路由,但如果副本失效,则需要将用户路由到其他副本。解决的是从库之间的数据不一致。
 
前缀一致读
notion image
第三者观察前两者对话,由于网络有延迟,回答的记录先写入,而问题的记录后写入。先看到果后看到因的现象。这种情况一般是由于分区或分片中的问题,不同分区记录事件的顺序不一致
前缀一致读保证对于一系列按照顺序发生的写请求,那么读取这些内容时也会按照写入顺序。可以将带有因果关系顺序关系的系列事件交给同一个分区完成。但效率较低。
 
复制滞后的解决方案和事务的提出
系统应该回答这样一个问题:如果复制延迟几分钟甚至几小时,那么应用层的行为会是什么样子?如果答案是没事就算了,否则就要考虑使用更强的一致性,比如写后读。
应用层可以处理这些特殊情况比如在主节点上进行读取,但代价是代码中处理这些问题通常比较复杂,容易出错。这也是数据库事务存在的原因,事务保证了更强的一致性,解放了应用。
 

多主节点复制

主从复制缺点是只有一个主节点,所有写入必经主节点。多主复制即存在多个主节点,同时接收写操作,同时每个主节点还扮演其他主节点的从节点。适用于如下场景:多数据中心、离线客户端操作、协作编辑。
 
处理写冲突
notion image
在多主结构下用户1将A改成B,用户2将A改成C,两个用户分别在两个主节点上写入都成功了,之后两个主节点相互同步数据,此时发生冲突。
  • 同步检测冲突:等待写请求完成对所有副本的同步后再通知用户写入成功,但这样就失去了多主允许多个主节点独立接受请求的意义,还不如直接用单主从复制模型。
  • 避免冲突:处理冲突最理想的策略就是避免发生冲突,应用层保证将特定记录的写入路由到同一主节点。
  • 收敛于一致状态:给每个请求分配唯一 ID,选择最高 ID的写入作为获胜者并丢弃其他写入,称为最后写入者获胜 last write wins LWW。但是容易造成数据丢失。或者将冲突合并在一起,如 B/C 这种状态。
  • 自定义冲突解决逻辑:支持应用层编写代码来解决冲突,可以在写入或读取时执行这些代码逻辑。相当于事件触发回调函数
 
多主同步拓扑结构
notion image
 

无主节点复制

todo
 

总结

复制和多副本主要服务于以下目的:
  • 高可用:即使部分机器出现故障,整个系统也能保持正常运行。
  • 连接断开与容错:运行应用程序出现网络中断时继续工作。
  • 低延迟:将数据放置在距离用户较近的地方,从而实现更快地交互。
  • 可扩展性:采用多副本读取,大幅提升系统读操作吞吐量。
复制简单来说就是在多台机器上保存多份相同的数据副本。看似简单的目标实践中包含了许多非常烧脑的问题,其中最最重要的是要考虑如果出现节点不可用或网络中断时系统应该如何处理。
 
三种副本方案:
  • 主从复制
  • 多主节点复制
  • 无主节点复制
 
复制可以是同步的,也可以是异步的。
  • 同步复制优点是数据一致性更好,缺点是吞吐量较低。
  • 异步复制优点是吞吐量较高,缺点是如果出现复制滞后问题,而且如果主节点挂掉,可能会出现数据丢失问题。
全同步不太适合,所以复制滞后是必须考虑在内的问题了。
 
应对复制滞后的处理办法:
  • 写后读一致性:写后的读走主库,解决主从复制延迟出现的写后读不到的问题。
  • 单调读:读永远走固定一个从库,解决从库之间数据不同步的问题,即两次读结果不一样。
  • 前缀一致读:保证数据之间的因果关系,总是以正确的顺序读取。
 
 

© 菜皮 2020 - 2024