当前位置: 首页 > news >正文

数据复制问题及其解决方案

主节点与从节点

每个保存数据库完整数据集的节点称之为副本。主从复制的原理如下:

  1. 指定某一个副本为主节点,客户写数据库时,写请求先发送给主节点,主节点写入本地存储;
  2. 主节点写入本地存储后,将数据更改以日志或更改流发送给从节点。从节点获得日志后应用到本地,并严格保持与主节点相同的写入顺序;
  3. 客户查询数据库可以从主节点或从节点查询,但写请求只能由主节点接受和完成。

同步复制与异步复制

同步复制

主节点写入数据后,同步到从节点,等从节点确认完成同步之后,主节点才会确认完成了写入。

同步复制的优点是:必定存在一个从节点与主节点数据一致,当主节点故障之后,可以快速将从节点切换为主节点;

同步复制的缺点是:从节点主接收复制日志之前有一段延迟,一般情况下这个延迟很短,但我们无法保证一定会在多长时间内完成复制。

根据同步复制的节点数量,我们可以将同步模式分为:

  1. 半同步:1 个节点同步复制,其它节点异步复制;
  2. 全同步:全部节点都同步复制;

实现全同步模式不切实际,实践中一般使用半同步。

(全)异步模式

异步模式即所有的从节点都为异步模式,这种模式的好处是,不管从节点上的数据多么滞后,主节点总是可以继续响应请求,系统吞吐量更好。异步模式的缺点是,如果主节点发生故障且无法恢复,则所有尚未复制到从节点的写请求都会丢失,即使向客户端确认了写操作·,但无法保证数据持久化。

异步模式这种弱持久性看起来不靠谱,但是还是广泛使用,特别是那些从节点数量巨大或者分布于广域地理环境。后面 ‘复制滞后问题’会继续这个话题。

配置新的从节点

操作步骤:

  1. 某个时间点,主节点产生一个快照,这样可以避免长时间锁定整个数据库(如 Redis RDB);(另一篇文章:Redis 复制)
  2. 将此快照拷贝到新的从节点;
  3. 从节点连接到主节点,然后请求快照点之后所发生的数据更改日志。
  4. 获得日志之后,从节点应用快照点孩子会的所有数据变更,这个过程称为追赶。

处理节点失效

系统中任何节点都有可能发生故障,如果能在系统不停机的情况下重启某个节点,会提高系统的可用性。下面分为从节点失效和主节点失效两种情况:

从节点失效

从节点保持着主节点发来的数据变更日志,如果从节点出现故障后顺利重启,可以根据日志得知最后一个同步的事务,这样连接上主节点后只需要请求该事务之后的数据变更即可完成追赶。

主节点失效

主节点失效后,需要选择某个从节点成为新的主节点,同时客户端后续会将写请求发送到新的主节点。从节点也会接收来自新的主节点的日志。

故障切换可以通过手动或者自动,自动切换的步骤为:

  1. 确认主节点失效,这里出错的可能性比较多,没有万无一失的方法能够确认问题出现在哪,所以一般基于超时的机制。
  2. 选举新的主节点。可以通过选举的方式,或者之前指定的某个节点作为主节点。候选主节点的数据最好是和主节点的差距最小,这样可以最小化数据丢失的风险和影响。
  3. 重新配置系统使主节点生效。客户端现在需要将写请求发送给新的主节点。

然而,上面的切换过程存在变数:

  1. 如果使用了异步复制,且新的主节点并未收到旧主节点的全部数据,在选举之后,新的主节点又重新上线,这时候新的主节点可能会收到冲突的写请求,这是因为旧的主节点还未意识到自己角色变化,还会尝试同步其它节点;常见的解决方式是:旧主节点上未完成的复制的写请求就此丢弃,但这可能会违背数据更新持久化的承诺;
  2. 如果新、旧节点之间数据未完成同步,并且外部系统依赖于数据库的内容并协同使用,丢弃数据的方案就会很危险。
  3. 某些情况下,可能会出现两个节点同时认为自己是主节点,这种情况称为脑裂,两个节点都能接受写请求,并且没有很好的解决办法,最后数据可能丢失或者破坏。一种安全应急方法是强制关闭其中一个节点。
  4. 如何合理设置超时时间来检测主节点失效?过长则意味恢复时间越长,过短款呢会导致很多不必要的切换。

复制日志的实现

基于语句的复制

主节点将执行的每个写请求的操作语句作为日志发给从节点,从节点执行这些 SQL 语句。

此种方式缺点比较多:

  1. 调用不确定函数如 NOW()等,会在不同节点生成不同的值;
  2. 如果语句使用了自增列,或者依赖数据库现有逻辑(例如,UPDATE … WHERE <某些条件> ),则副本必须严格按照主节点语句的执行顺序,这样的话如果有同时并发的事务,将会有很大的限制;
  3. 有副作用的语句,如触发器、存储过程等,可能会在不同节点产生不同的副作用;

基于预写日志 (WAL)传输

所有对数据库写入的字节序列都会记入日志,因此可以将这种日志发送给节点构建完全相同的副本。

此方式的主要缺点是:日志描述的数据结果非常底层:一个 WAL 包含了哪些磁盘块的哪些字节发生变化这些类似的细节,这使得复制方案与存储结构紧密耦合。如果数据库的存储格式从一个版本切换为另一个版本,那么系统通常无法支持主从节点运行不同版本的软件。

基于行的逻辑日志复制

复制与存储引擎采用不同的数据格式,这样复制与存储逻辑分离。这种复制的日志称为 逻辑日志,用于区分物理存储的数据表示。(MYSQL的 binlog 就是逻辑日志,redo log 是物理日志)

逻辑日志通常是用 一系列的记录来描述数据表行级别的写请求:

  1. 对于行插入,日志需要包含所有相关列的新值
  2. 对于行删除,必须有足够的信息来唯一标识已删除的行,通常是靠主键,但如果表没有主键,就需要记录所有列的旧值;
  3. 对于行更新,日志需要足够的信息来唯一标识更新的行,以及所有列的新值(或至少包含所有已更新列的新值)

如果一条事务涉及多行的修改,则会产生多个这样的日志,并且在后面跟着一条记录,标识该事务已提交。

此方式的优点是:

  1. 逻辑日志与存储逻辑分离,更容易向后兼容;
  2. 对外部应用程序来说,逻辑日志更容易处理;比如可以用逻辑日志来进行离线分析,或者通过消费 binlog 来实现存储与缓存的最终一致性等。

基于触发器等复制

上面三种方式都是数据库层面去完成的,如果想将复制控制权交给应用层,那么可以考虑使用触发器复制。触发器支持注册自己的应用层代码,通过触发器技术,可以将数据更改记录到另一个表。

这种方式的优点是较为灵活,缺点则是开销更高,或者暴露一些限制。

复制滞后性问题

使用复制可以提高三个方面的能力:

  1. 可用性:单个节点故障的情况下还有其它节点可用;
  2. 可扩展性:处理更多的请求
  3. 低延迟:从节点分布于不同的地理环境,请求可以就近访问,延迟更低

在从节点比较多的情况下不使用全同步模式,因为只要任一个从节点出现问题就会影响整个系统。

通常使用半同步或者全异步模式。

如果应用从一个异步同步的节点读取信息,且该节点比主节点落后,那么可能会读到旧的、过期的信息。

复制滞后会带来几种问题,以下介绍几种情况并提供对应的解决思路。

解决复制滞后性问题

读自己的写

考虑以下问题:

在这里插入图片描述

现在有一个主节点,两个从节点,使用半同步复制模式,从节点 1 使用同步复制,从节点 2 采用的是异步复制。首先用户插入一条数据,在同步至从节点 1 后即返回。在未同步至从节点2时,发起查询,刚好是由从 节点 2 来读取,这时候发现刚才插入成功的内容却看不到。

对于这种情况,我们需要 “写后读一致性”,也就是读写一致性。这个机制保证,即使用户重新加载,也总是能看到自己最近提交的更新。但对于其他用户来说,可能会在稍后才能看到。

  1. 如果用户可能读取到自己修改的内容,就从主节点读;这种方式需要在查询之前就知道内容是否可能会被修改。比如个人主页信息只能自己修改。那么当读自己的主页信息时就从主节点读,读别人的主页信息的时候就从从节点读。
  2. 如果大部分内容都能被所有人修改,那么大部分请求都得请求写请求,这样就丧失了读操作的扩展性。此时可以通过节点复制滞后程度来选择节点,比如如果请求到的节点的复制滞后时间在 1分钟内才读这个节点,否则交给其他节点。客户端也可以记住其最近更新的时间戳,满足复制滞后时间的节点才处理该请求。

如果同一用户可能从多个设备如手机、电脑读取数据,那么还有一些需要考虑的问题:

  1. 通过客户端记录时间戳的方式实现比较困难,因为一台设备无法得知另一台设备发生了什么;这时候元数据就需要实现共享。
  2. 如果副本分布在多个中心,无法保证不同设备路由到同一个数据中心。因此还需要额外的机制保证设备能路由到同一个数据中心。

单调读

考虑这种情况,

在这里插入图片描述

依然是用户先插入一条数据,随后复制至从节点 1 和从节点 2。在从节点 1 复制完成之后读取了从节点 1,然后在从节点 2 复制完成之前读取了从节点 2 ,这时候就会发现,明明读到了数据,再去读又读不到了。

单调读可以解决这种问题:每个用户总是从同一个副本读取,不同用户可以读不同副本。可以使用用户 ID 进行哈希来确定固定的节点。

前缀一致读

(这个问题的解决思路比较绕,后续补充)

多节点复制

多节点复制即配置多个主节点,每个主节点都可以接受写操作,同时将对数据的更改转发到其它节点,同时也接受来自其它节点数据的变更。多主节点主要是用于多数据中心的环境下。

在多数据中心的环境下,多主节点复制与主从复制的对比如下:

  1. 性能:对于主从复制,每个写请求都需要经过广域网到达主节点的数据中心,这样会大大增加延迟,并且偏离了采用多数据中心的初衷(即就近访问)。对于多主节点模型,每个写请求可以主本地数据中心快速响应,然后采用异步复制的方式同步到其它数据中心。因此对于上层应用有效屏蔽了数据中心之间的网络延迟,终端用户体验更好。
  2. 容忍数据中心失效:对于主从复制,如果主节点所在的数据中心发生故障,那么需要切换至其它数据中心,将其中一个从节点提升为主节点。对于多节点模型,每个数据中心独立于其它数据中心,发生故障的数据中心在恢复之后更新到最新状态。
  3. 容忍网络问题:数据中心之间距离较远,通信通常经由广域网,远不如数据中心内部的本地网络可靠。由于主从复制模型的写请求是同步操作,对数据中心之间的网络性能和稳定性更加依赖。多主节点模型的数据中心之间是异步复制,可以更好容忍这种问题。

多主节点模型存在一种缺点:多个数据中心可能修改相同的数据,因此还需要解决冲突问题。

离线客户端操作

另一种适合多主复制的场景是,应用与网络断开后还需要继续工作。比如手机日历应用程序等,无论当前是否联网,对于写请求(比如添加会议)或者读请求(查看会议)都需要成功。等待下次网络连接后,可以跟其它设备的数据进行同步。这里可以把手机本身当成一个数据中心。

处理写冲突

多主节点复制的最大问题就是可能发生写冲突。必须有有效的方案来解决冲突。
eg:

  1. 用户 1 将标题从 A 改为 B,写请求是在数据中心 1 完成;
  2. 用户 2 将标题从 A 改为 C,写请求是在数据中心 2 完成;

这种情况下两个请求都会成功执行,但是在两个数据中心数据同步时就会出现冲突。

尝试避免冲突

处理冲突最理想的方式是避免冲突。如果应用层可以保证对特定记录的写请求总是请求到同一个主节点,这样就不会发生冲突。比如:可以根据地理环境,不同的用户可能对应不同的数据中心,从用户的角度来讲,基本等价于主从复制。

但是还是存在一些问题:比如

  1. 用户的地理环境可能存在漂移的情况,这样其路由到的数据中心可能就不同了。
  2. 有时候需要事先改变指定的主节点,比如某个数据中心故障了,那么需要将请求路由到其它的数据中心。

此时,冲突避免的方式不再有效。

收敛于一致状态

如果无法避免冲突,那么还是需要保证最终两个数据中心的数据一致。有以下可能的解决方式:

  1. 给每个写操作分配一个唯一的 ID、常见的比如时间戳,然后挑选最高的 ID 进行写入,并将其它写入丢弃。如果基于时间戳,这种技术称为 最后写入者获胜。虽然这种方式很流行,但容易造成数据丢失。
  2. 为每个副本分配唯一 ID,并制定规则,比如序号高的副本写入始终优先于序号低的副本。
  3. 以某种方式将这些值组合在一起。(看起来比较奇怪,应该很少使用这种方式)
  4. 利用预定义好的格式来记录和保留冲突相关的所有信息,然后依靠应用层的逻辑,事后解决冲突(可能会提示用户)

待续。

注:本文主要来源于《数据密集型应用系统设计》,用于学习记录。本章节内容较多,后续会继续补充。阅读过程中有问题欢迎评论区评论,共同进步~

http://www.dtcms.com/a/486161.html

相关文章:

  • Java-Spring入门指南(二十五)Android 的历史,认识移动应用和Android 基础知识
  • WPF依赖属性(Dependency Property)详解
  • 深度学习进阶(三)——生成模型的崛起:从自回归到扩散
  • 三门峡网站开发ict网站建设
  • 神经网络之链式法则
  • C#设计模式源码讲解
  • 性能测试单场景测试时,是设置并发读多个文件,还是设置不同的用户读不同的文件?
  • Qt初识(对象树,乱码问题,小结)
  • 基于Home Assistant的机器人低延迟通信项目详细调研报告
  • 深圳网站做的好的公司婚庆网站开发目的
  • 中小企业网站制作是什么宁德网站建设51yunsou
  • 代理模式 vs AOP:支付服务中的日志增强实践(含执行顺序详解)
  • linux系统运维教程,linux系统运维攻略
  • string字符集
  • Linux 命令:fsck
  • 如何提升生物科技研发辅助的效率?
  • ECEF坐标转换库
  • 企业商务网被公司优化掉是什么意思
  • 网站虚拟主机购买教程专业网站设计工作室
  • 数据库管理-第376期 Oracle AI DB 23.26新特性一览(20251016)
  • 【Nature高分思路速递】 物理驱动的机器学习
  • word文档转pdf开源免费,可自定义水印
  • k8s(五)PV和PVC详解
  • 深度学习与自然语言处理
  • python 部署可离线使用的中文识别OCR(window)
  • 湖州微信网站建设网站301了不知道在哪做的
  • 请描述网站开发的一般流程图php网站开发经理招聘
  • 关于pkg-config的使用示例--g++编译过程引入第三方库(如Opencv、Qt)
  • 外贸soho先做网站wordpress如何把背景颜色调为白色
  • zk02-知识演进