分布式服务高可用实现:复制

2023-11-06

1. 为什么需要复制

我们可以考虑如下问题:

  1. 当数据量、读取或写入负载已经超过了当前服务器的处理能力,如何实现负载均衡?

  2. 希望在单台服务器出现故障时仍能继续工作,这该如何实现?

  3. 当服务的用户遍布全球,并希望他们访问服务时不会有较大的延迟,怎么才能统一用户的交互体验?

这些问题其实都能通过“复制”来解决:复制,即在不同的节点上保存相同的副本,提供数据冗余。如果一些节点不可用,剩余的节点仍然可以提供数据服务,这些节点可能部署在不同的地理位置,以此来改善系统性能,针对以上三个问题的解决方案如下:

  1. 采用无共享架构(shared-nothing architecture),进行横向扩展,将数据分散到多台服务器上,进行有效的负载均衡,提高服务的伸缩性
  2. 部署多台服务器,在一台宕机时,其他服务器能随时接管,实现服务的高可用
  3. 在多地理位置上部署服务,使用户能就近访问,避免产生较大的延迟,统一用户体验

复制概述.png

2. 单主复制

单主节点复制是工作中最常见的复制解决方案。存储了数据库拷贝的每个节点被称为副本(replica),每次向数据库的写入操作都需要传播到所有副本上,否则副本数据就会不一致,它的工作原理如下:

  • 其中一个副本被指定为领导者,也称为主库,当客户端要向数据库写入时,它必须将该请求发送给领导者
  • 其他副本被称为追随者,也被称为从库只读副本,每当领导者将数据写入本地存储时,它会将数据变更以复制日志变更流的形式推送给所有的追随者,并且追随者按照与领导者相同的处理顺序来进行写入

2.1 节点间的数据同步

数据的同步分同步复制异步复制,同步复制的好处是从库能够保证与主库有一致的数据,当主库失效时,这些数据能够在从库上找到,但是它的缺点也很明显:主库需要等待从库的数据同步结果,如果同步从库没有响应,主库就无法再处理新的写入操作,而是进入阻塞状态。

读多写少的场景下,我们通常会增加从节点的数量来对读请求进行负载均衡,但是如果此时所有从库都是同步复制是不实际的且不可靠的,因为单个节点的故障或网络中断都会影响数据的写入。

事实上数据库启用同步复制时,通常表示有一个从库是同步复制,其他从库是异步复制,当同步从库失效时,异步复制的副本会改为同步复制,这保证了至少有两个节点拥有最新的数据副本,这种配置也被成为半同步

而通常情况下,基于领导者的复制都配置为完全异步。如下图所示,用户1234修改picture_url信息时,从主库同步到从库是存在延迟的。

异步复制.png

这意味着如果此时主库失效而尚未复制给从库的数据会丢失,导致已经向客户端请求确认成功也不能保证写入是持久的,而且如果在主节点写入数据后,立即向Follower 2读取数据,则会读取到旧数据,给用户的感觉就像是刚才的写入丢失了一样,这对应了读己之写一致性问题,我们在后文会做具体解释。

但是实际生产情况下都基于异步复制,说明强一致性并不是必要的保证,而对保证系统吞吐量的需求更高。因为在这种机制下,即使从库已经远远落后,主库也不必等待从库写入完成就可以返回数据写入成功。之后从库会慢慢赶上并与主库一致,这种弱一致性的保证被称为最终一致性

2.2 复制延迟问题

从上一小节中,我们知道了异步复制在写入主库到复制到从库存在延迟,因此会产生一系列的问题,在这里我们对这些存在的问题进行更具体的解释。

  • 写入完成后主节点失效,但从节点未完成数据同步

主节点失效,需要进行故障转移,将一个从库提升为主库,主库的最佳人选通常是拥有最新数据副本的从库(zookeeper的事务ID比较过程遵从的这个原理),让新主库来继续为客户端服务,其他从库从新的主库节点进行数据同步。

如果此时新的主节点在旧的主节点失效前还未完成数据同步,那么通常的做法是将原主节点未完成复制的数据丢弃,此时就会发生数据丢失的问题。

而且在旧的主库恢复时,需要让它意识到新主库的存在,并使自己成为一个从库。如果当集群中出现多个节点认为自己是主节点时,即**“脑裂”**现象,是非常危险的:因为多个主节点都可以进行写操作,却没有冲突解决机制,数据就可能被破坏。

zookeeper出现脑裂时通过判断epoch的大小(故障转移完成新的一轮选举之后它的epoch会递增)来使从节点拒绝旧主节点的请求,保证数据不被破坏。


  • 写后读一致性(读己之写一致性)

写后读一致性.png

如上图所示,如果用户在写入后马上请求查看数据,则新数据可能尚未到达只读从库,看起来好像刚提交的数据丢失了,这种情况可以通过以下方式来解决

  • 对于用户可能修改过的内容,总是从主库读取,这需要有办法在不通过查询的方式来知道用户是否修改了某些数据。比如,社交网络的个人信息通常由个人来修改,因此可以定义总是从主库来读取自己的档案信息,读取其他人的信息则在从库获取
  • 如果应用中的大部分内容都能被用户修改,那么大部分查询都从主库读取的话,读伸缩性就没有效果了。在这种情况下可以通过记录上次更新的时间,比如在更新后的一分钟内从主库查询,之后在从库读取,以此来保证读伸缩性
  • 客户端记录最近一次的写入时间戳,系统需要确保从库在处理该用户的读请求时,该时间戳的变更已经在本从库中记录了,如果查询的当前从库不存在该记录,那么需要再从其他从库读取,或者等待从库同步数据

  • 单调读

单调读.png

如上图所示,用户1234写入了一条评论,用户2345在读取其他用户添加的评论时,第一次请求到了Follower1,这时从库已经完成了数据同步,那么能读取到该评论。但是第二次请求到了Follower2,而Follower2并没有完成数据同步,导致看不到之前读取到的评论,出现**“时间倒流”**现象。

避免这种现象需要保证单调读,即当用户读取到较新的数据时,他不会再读取到更旧的数据。实现单调读的方式是使同一个用户的读请求都请求到同一个副本节点,我们可以根据ID的散列来分配副本而不是随机分配。

2.3 新从库的数据同步

通常为了增强系统的读伸缩性,会添加新的从库。但新从库在与主库做数据同步时,简单地将数据文件复制到另一个节点通常是不够的,因为数据总是在不断的变化,当前的数据文件不能包含全量数据,所以一般情况下的流程如下:

  1. 获取某个时刻的主库一致性快照,并将该快照复制到新的从库节点
  2. 从库连接到主库,并拉取数据快照之后发生的数据变更,这就要求快照与主库复制日志有精确的位置关联,Mysql是通过binlog coordinates二进制日志坐标来关联的
  3. 从库处理完快照之后的数据变更,那么就说它赶上了主库,现在它就可以及时处理主库的数据变化了

如果发生从库失效,在从库重新启动后会执行以上2,3步骤,通过日志可以知道发生故障之前处理的最后一个事务,通过该记录请求从库断开期间的所有数据变更,慢慢地追赶主库。

3. 多主复制

基于单主节点的复制,每个写请求都要经过主节点所在的数据中心,那么随着写入请求的增加,单主节点伸缩性差的局限性就会显现出来,而且在世界各地的用户都需要请求到该主节点才能进行写入,可能存在延时较长的问题。为了解决这些问题,在单主节点架构下进行延伸,自然是多主节点复制,在这种情况下,每个主节点又是其他主节点的从库。

通常情况下,增加单主节点的伸缩性不会使用多主复制,而是通过数据分区来解决。因为前者导致的复杂性已经超过了它能带来的好处,不过在某些情况下,也是可以采用多主复制的。

多数据中心的多主复制架构如下图所示:

多主复制.png

数据库的副本分散在多个数据中心,在每个数据中心都有主库,在每个数据中心内都是主从复制,每个数据中心的写请求都会在本地数据中心处理然后同步到其他数据中心的主节点,这样数据中心间的网络延迟对用户来说就变成了透明的,这意味着性能可能会更好,对网络问题的容忍度更高;多数据中心部署在不同的地理位置上,对用户来说体验更好;如果本地数据中心发生故障,能够将请求转移到其他数据中心,等本地数据中心恢复并复制赶上进度后,能继续提供服务。

3.1 多主复制的应用场景

  • 断网后仍继续工作的应用程序

如果你使用的手机和电脑是同一个生态的话,那么一般情况下,备忘录内容的修改能在设备之间进行同步。从架构的角度来看,每个设备都相当于是一个数据中心,每个数据中心都能进行写入,它符合多主复制模型。数据中心间的网络是极度不可靠的,当手机离线,在电脑端对备忘录进行修改后,那么当手机再接入互联网,需要完成设备间的数据同步,这就是异步多主复制的过程。


  • 在线协同文档

当有用户对文档进行编辑时,所做的更改将立即被异步复制到服务器和其他任何正在使用该文档的用户,每个用户操作的文档都相当于是一个数据中心,这种情况与我们上文所述的在离线设备上对备忘录进行修改有相似之处。不过,在这种情况下,为了加速协同和提高文档的使用体验,需要解决同时编辑产生的写入冲突问题。

3.2 解决写入冲突

虽然我们在上文中提到了多主复制能带来诸多好处(多主带来的伸缩性、更好的容错机制和减少地理位置造成的延时),但是相伴的配置复杂写入冲突问题也是需要我们直面的。

如下图所示,用户1修改标题为B,用户2修改标题为C,那么此时就会发生写入冲突,我们很难说得清楚将谁的结果指定为最终修改结果是合适的,但是我们还是不得不将多主数据库的值收敛至一致的状态。

多主复制冲突.png

最后写入胜利(LWW,last write wins)是比较常用的方法,我们可以为每个请求增加时间戳或者唯一的ID,挑选其中较大的值作为最终结果,并将其他的值丢弃,不过这种情况容易造成数据丢失,比如在分布式服务中存在的不可靠的时钟问题,可能后写入的值反而携带的时间戳更靠前,那么这种情况下就会将我们预期被写入的结果丢弃。

另一种方法是可以为每个主库分配一个ID编号,具有更高的ID编号的主库具有更高的优先级,但是这也会产生数据丢失问题。

如果不想发生数据丢失,可以以某种组合的方式将这些值组合在一起。以上图中对标题的修改为例,可以将标题修改结果拼接成 B/C,不过这种情况需要用户对结果进行修正。和该方式类似的,还可以考虑将所有对数据修改的冲突都显示的记录下来,之后提示用户进行修改。

版本向量也是一种解决冲突的方式。以缓存为例,我们为每个键维护一个版本号,每次写入时先进行读取,并且必须将之前读取的所有值合并在一起,其中删除的值会被标记(墓碑),这样就能够避免在合并完成后仍然出现曾删掉的值。在写入完成后版本号递增,将新版本号与写入的值一起存储。在多个副本并发接受写入时,每个副本也需要维护版本号,每个副本在处理写入时增加自己的版本号。所有副本的版本号集合称为版本向量,版本向量会随着读取和写入在客户端和服务端之前来回传递,并且允许数据库区分覆盖写入和并发写入。版本向量能够确保从一个副本读取并随后写回到另一个副本是安全的

不过,虽然我们介绍了这么多解决冲突的方式,但是实际上避免冲突是最好的方式。比如我们可以确保特定记录的所有写入都通过同一个主库,那么就不会发生冲突了。

关于并发的理解:如果是在单体服务中,我们可以通过时间戳来判断两个事件同时发生;如果是在分布式系统中,因为分布式系统存在不可靠的时钟问题,所以在实际的系统中很难判断两个事件是否是同时发生,所以并发在字面时间上的重叠并不重要。实际上,并发强调的是两个事件是否能意识到对方的存在,如果都意识不到对方的存在,即两个事件都不在另一个之前发生,那么这两个事件是并发的,那么它们存在需要被解决的并发写入冲突。

4. 无主复制

无主复制与单主、多主复制采用不同的复制机制:它没有主库和从库的职责差异,而是放弃了主库的概念,每一个数据库节点都可以处理写入请求,因此它适用于高可用、低延时、且能够容忍偶尔读到陈旧值的应用场景。

这种复制模式还有一个好处是不存在故障转移,当某个节点宕机时,应用会将该请求转发到其他正常工作的节点。等到宕机节点重新连接之后,该节点可以通过以下两种方式赶上错过的写入:

  • 读修复:适用于读频繁的值,客户端并行获取多个节点时,如果它检测到陈旧的值,那么将读取到的新值把陈旧的值覆盖掉
  • 反熵:开启后台进程,该进程不断查找副本之间的数据差异,并将任何缺少的数据从一个副本复制到另一个副本

无主复制的每个数据库节点都能处理读写请求,但是并不是在某单个节点写入完成后就被认定为写入成功或在单个节点读取就认为该值是读取结果。它的读写遵循法定人数原则,与zookeeper处理写入请求使用的容错共识算法类似。

一般地说,如果有n个副本,每个写入必须由 w 个节点确认才能被认为是成功的,并且每个读取必须查询 r 个节点。只要w + r > n,我们可以预期在读取时获得最新的值,因为在 r 个读取中至少有一个节点是最新的,遵循这些 r 值和 w 值的读写被称为法定人数读写。常见的配置是将n(节点数)配置成奇数,并设置w = r = (n + 1) / 2向上取整,这样保证了写入和读取的节点集合必然有重叠,所以读取的节点中必然至少有一个节点具有最新的值。

如下图所示,用户1234会将写入请求发送到所有的3个数据库副本,并且在其中两个副本返回成功时即认为写入成功,而忽略了宕机副本错过写入的事实;用户2345在读取数据时,也会将请求发送到所有副本,并将其中最新的值看作读取的结果。

无主复制的读写.png

每种复制的模式都有优点和缺点,单主复制是比较流行的,它容易理解而且无需处理冲突问题(写入只有主节点处理)。不过在节点故障或者网络出现较大的延时时,多主复制和无主复制可以更加健壮,但是它们只能提供较弱的一致性保证。


巨人的肩膀

作者:京东物流 王奕龙

来源:京东云开发者社区 自猿其说Tech

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

分布式服务高可用实现:复制 的相关文章

  • location.href&&window.open

    目录 location对象的定义和用法 常用的几种方法 window open 参数 返回值 document write Window focus 实例 location对象的定义和用法 href 属性是一个可读可写的字符串 可设置或返回

随机推荐

  • 系统提示“d3dx9_43.dll丢失、找不到”原因与解决方案

    相信很多朋友都有遇到过这种情况 在登陆游戏或打开某个软件时 系统会弹出一个窗口提示 d3dx9 43 dll丢失 或 d3dx9 43 dll找不到 出现这种情况一般是系统的不完整性导致 非正版系统比较容易出现此问题 也可能是此文件被误删
  • LeetCode——反转字符串中的元音字母

    LeetCode 反转字符串中的元音字母 题目描述 编写一个函数 以字符串作为输入 反转该字符串中的元音字母 示例 1 输入 hello 输出 holle 示例 2 输入 leetcode 输出 leotcede 提示 元音字母不包含字母
  • 常用的计算机技术检索有哪几种,在计算机检索中,常用的布尔逻辑算符有哪几种...

    在计算机检索中 常用的布尔逻辑算符有哪几种 答案 1 信息版本 手机版 解决时间 2018 12 25 13 02 已解决 2018 12 24 15 15 在计算机检索中 常用的布尔逻辑算符有哪几种 最佳答案 2018 12 24 16
  • 【C++】程序的内存模型-内存四区

    1 内存分区模型 2 代码区 3 全局区 4 栈区 指针相关知识 https blog csdn net daidai10086 article details 118893241 5 堆区 6 new运算符
  • 启动Microsoft Office时出现“无法验证该产品的许可证“Office 应用

    项目 2021 12 09 适用于 Office Professional Plus 2016 Office Standard 2016 Access 2016 Excel 2016 Microsoft Publisher 2016 One
  • JS加密在网页安全中的应用及逆向分析方法

    在当今互联网时代 网页加密技术的应用越来越广泛 在网页中 常见的加密方式包括三种 flash加密 密码控件加密和JS加密 虽然这三种方式各有优劣 但JS加密是目前最为普遍和灵活的加密方式 JS加密的广泛应用主要源于它的灵活性和易用性 与fl
  • vue-cli3搭建及vue.config.js配置(配置文件完整且不报错)

    如果你已经安装了vue 2 9 6 的话 则需要先将其删除指令为 npm uninstall g vue cli 第二步 更新Vue cli 指令 npm install g vue cli 或 cnpm install g vue cli
  • 2023最新渗透学习路线,手把手带你从零基础进阶渗透攻防工程师

    要成为一名渗透测试员 想从基础学习需要先掌握下面这3块 文末有相关自学资源推荐 1 学习硬件和网络 渗透测试主要涉及网络和部分涉及硬件 2 操作系统和系统架构 操作系统和系统架构在渗透测试中起着关键作用 系统操作涉及x86 32位 和x64
  • RSA算法研究报告

    摘 要 分析RSA算法的应用现状 论证文件加密应用RSA算法的可行性和意义 设计一套完整实用的RSA文件加密解决方案 具体编码实现 对RSA算法进行研究 从常规RSA算法出发 用C 实现RSA加密算法类库 并在32位windows平台封装成
  • python如何编程-如何入门Python编程

    Python目前已经成为受欢迎的编程语言之一 吸引了一大批粉丝 但是很多人在要跨入的时候犹豫了 原因竟然是觉得学习Python太难了 真的是这样吗 入门Python编程学习有什么好的技巧吗 Python因为其清晰易读的风格 广泛的适用性 P
  • Golang 解决跨域问题, 处理http设置header无效

    今天遇到跨域问题 设置了如下配置 w WriteHeader http StatusOK w Header Set Access Control Allow Origin w Header Add Access Control Allow
  • 【数据库】第六章总结-关系数据理论

    如何设计一个好的表的结构 通过 关系数据库的规范化理论 我们简化一个 关系模式 R
  • python--字典版学生成绩管理系统

    目录 全局变量定义 菜单方法 添加学生信息 删除学生信息 修改学生成绩 查找学生信息 统计学生最高分 平均分 最低分 主运行函数 全部源代码 实现对学生信息的添加 删除 修改 查询操作 全局变量定义 import math 定义一个空字典存
  • Fractional-strided convolution,分数阶卷积,也称为转置卷积或反卷积

    Fractional strided convolution 分数阶卷积 也称为转置卷积或反卷积 是一种用于深度学习和计算机视觉的技术 用于对输入图像进行上采样或增加空间分辨率 与执行下采样的常规卷积层不同 分数步幅卷积层执行上采样 在常规
  • 奇偶交换排序

    已知奇偶交换排序如下所述 1 第一趟对序列中所有奇数项i扫描 将a i 和a i 1 进行比较 2 第二趟对序列中所有偶数项i扫描 将a i 和a i 1 进行比较 3 每次比较时若 a i gt a i 1 则将两者交换 4 第三趟对所有
  • 读取excel文件转为HTML

    最近写了一个公式较复杂的报表 因梳理公式与后期的数据核对会比较耗费时间 因此采用了读取EXCEL模板 然后填写数据 最后读取最终文件生成页面的方法 现整理了将EXCEL转为HTML的方法 本人在用的xlsx文件 颜色不支持灰色 建议使用其他
  • el-date-picker日期限定范围

    el date picker日期限定范围 页面
  • 一文看懂RPA的技术架构及原理

    一文看懂RPA的技术架构及原理 众所周知 作为一款软件或平台 RPA 机器人流程自动化 是用来替代人类员工实施基于规则的高度重复性工作的程序 而非实体存在的流程处理机器 但由于它的新颖性 许多人可能会对RPA及其构成感到困惑 典型的RPA平
  • VS 2015 新建QT项目

    打开VS 2015 文件 新建 新建项目 选择QT5 Projects QT Application 没有出现上述菜单的 请重新安装Visual Studio Add in 1 2 4 for Qt5 并重启VS QT创建向导 选择必要组件
  • 分布式服务高可用实现:复制

    1 为什么需要复制 我们可以考虑如下问题 当数据量 读取或写入负载已经超过了当前服务器的处理能力 如何实现负载均衡 希望在单台服务器出现故障时仍能继续工作 这该如何实现 当服务的用户遍布全球 并希望他们访问服务时不会有较大的延迟 怎么才能统