中文建网站,电子商务网站开发与实训答案,php做电子商城网站,公司简介电子版宣传册模板分布式服务高可用实现#xff1a;复制
1. 为什么需要复制
我们可以考虑如下问题#xff1a;
当数据量、读取或写入负载已经超过了当前服务器的处理能力#xff0c;如何实现负载均衡#xff1f;希望在单台服务器出现故障时仍能继续工作#xff0c;这该如何实现#xff…分布式服务高可用实现复制
1. 为什么需要复制
我们可以考虑如下问题
当数据量、读取或写入负载已经超过了当前服务器的处理能力如何实现负载均衡希望在单台服务器出现故障时仍能继续工作这该如何实现当服务的用户遍布全球并希望他们访问服务时不会有较大的延迟怎么才能统一用户的交互体验
这些问题其实都能通过 “复制” 来解决复制即在不同的节点上保存相同的副本提供数据冗余。如果一些节点不可用剩余的节点仍然可以提供数据服务这些节点可能部署在不同的地理位置以此来改善系统性能针对以上三个问题的解决方案如下
采用无共享架构shared-nothing architecture进行 横向扩展将数据分散到多台服务器上进行有效的 负载均衡提高服务的 伸缩性部署多台服务器在一台宕机时其他服务器能随时接管实现服务的 高可用在多地理位置上部署服务使用户能就近访问避免产生较大的延迟统一用户体验
2. 单主复制
单主节点复制 是工作中最常见的复制解决方案。存储了数据库拷贝的每个节点被称为 副本replica 每次向数据库的写入操作都需要传播到所有副本上否则副本数据就会不一致它的工作原理如下
其中一个副本被指定为 领导者也称为主库当客户端要向数据库写入时它必须将该请求发送给领导者其他副本被称为 追随者也被称为 从库 或 只读副本每当领导者将数据写入本地存储时它会将数据变更以 复制日志 或 变更流 的形式推送给所有的追随者并且追随者按照与领导者 相同的处理顺序 来进行写入
2.1 节点间的数据同步
数据的同步分 同步复制 和 异步复制同步复制的好处是从库能够保证与主库有一致的数据当主库失效时这些数据能够在从库上找到但是它的缺点也很明显主库需要等待从库的数据同步结果如果同步从库没有响应主库就无法再处理新的写入操作而是进入阻塞状态。
在 读多写少 的场景下我们通常会增加从节点的数量来对读请求进行负载均衡但是如果此时所有从库都是同步复制是不实际的且不可靠的因为单个节点的故障或网络中断都会影响数据的写入。
事实上数据库启用同步复制时通常表示有一个从库是同步复制其他从库是异步复制当同步从库失效时异步复制的副本会改为同步复制这保证了至少有两个节点拥有最新的数据副本这种配置也被成为 半同步。
而通常情况下基于领导者的复制都配置为 完全异步。如下图所示用户1234修改picture_url 信息时从主库同步到从库是存在延迟的。
这意味着如果此时主库失效而尚未复制给从库的数据会丢失导致已经向客户端请求确认成功也不能保证写入是持久的而且如果在主节点写入数据后立即向 Follower 2 读取数据则会读取到旧数据给用户的感觉就像是刚才的写入丢失了一样这对应了 读己之写一致性 问题我们在后文会做具体解释。
但是实际生产情况下都基于异步复制说明强一致性并不是必要的保证而对保证系统 吞吐量 的需求更高。因为在这种机制下即使从库已经远远落后主库也不必等待从库写入完成就可以返回数据写入成功。之后从库会慢慢赶上并与主库一致这种弱一致性的保证被称为 最终一致性。
2.2 复制延迟问题
从上一小节中我们知道了异步复制在写入主库到复制到从库存在延迟因此会产生一系列的问题在这里我们对这些存在的问题进行更具体的解释。
写入完成后主节点失效但从节点未完成数据同步
主节点失效需要进行 故障转移将一个从库提升为主库主库的最佳人选通常是拥有最新数据副本的从库zookeeper的事务ID比较过程遵从的这个原理让新主库来继续为客户端服务其他从库从新的主库节点进行数据同步。
如果此时新的主节点在旧的主节点失效前还未完成数据同步那么通常的做法是将原主节点未完成复制的数据丢弃此时就会发生 数据丢失 的问题。
而且在旧的主库恢复时需要让它意识到新主库的存在并使自己成为一个从库。如果当集群中出现多个节点认为自己是主节点时即 “脑裂” 现象是非常危险的因为多个主节点都可以进行写操作却没有冲突解决机制数据就可能被破坏。 zookeeper出现脑裂时通过判断 epoch 的大小故障转移完成新的一轮选举之后它的epoch会递增来使从节点拒绝旧主节点的请求保证数据不被破坏。 写后读一致性读己之写一致性
如上 图所示如果用户在写入后马上请求查看数据则新数据可能尚未到达只读从库看起来好像刚提交的数据丢失了这种情况可以通过以下方式来解决
对于用户 可能修改过 的内容总是从主库读取这需要有办法在不通过查询的方式来知道用户是否修改了某些数据。比如社交网络的个人信息通常由个人来修改因此可以定义总是从主库来读取自己的档案信息读取其他人的信息则在从库获取如果应用中的大部分内容都能被用户修改那么大部分查询都从主库读取的话读伸缩性 就没有效果了。在这种情况下可以通过记录上次更新的时间比如在更新后的一分钟内从主库查询之后在从库读取以此来保证读伸缩性客户端记录最近一次的写入时间戳系统需要确保从库在处理该用户的读请求时该时间戳的变更已经在本从库中记录了如果查询的当前从库不存在该记录那么需要再从其他从库读取或者等待从库同步数据 单调读 如上图所示用户1234写入了一条评论用户2345在读取其他用户添加的评论时第一次请求到了 Follower1这时从库已经完成了数据同步那么能读取到该评论。但是第二次请求到了 Follower2而 Follower2 并没有完成数据同步导致看不到之前读取到的评论出现 “时间倒流” 现象。
避免这种现象需要保证 单调读即当用户读取到较新的数据时他不会再读取到更旧的数据。实现单调读的方式是使 同一个用户的读请求都请求到同一个副本节点我们可以根据ID的散列来分配副本而不是随机分配。
2.3 新从库的数据同步
通常为了增强系统的 读伸缩性会添加新的从库。但新从库在与主库做数据同步时简单地将数据文件复制到另一个节点通常是不够的因为数据总是在不断的变化当前的数据文件不能包含全量数据所以一般情况下的流程如下
获取某个时刻的主库一致性快照并将该快照复制到新的从库节点从库连接到主库并拉取数据快照之后发生的数据变更这就要求快照与主库复制日志有精确的位置关联Mysql是通过 binlog coordinates 二进制日志坐标来关联的从库处理完快照之后的数据变更那么就说它赶上了主库现在它就可以及时处理主库的数据变化了
如果发生 从库失效在从库重新启动后会执行以上 2,3 步骤通过日志可以知道发生故障之前处理的最后一个事务通过该记录请求从库断开期间的所有数据变更慢慢地追赶主库。
3. 多主复制
基于单主节点的复制每个写请求都要经过主节点所在的数据中心那么随着写入请求的增加单主节点伸缩性差的局限性就会显现出来而且在世界各地的用户都需要请求到该主节点才能进行写入可能存在延时较长的问题。为了解决这些问题在单主节点架构下进行延伸自然是 多主节点复制在这种情况下每个主节点又是其他主节点的从库。 通常情况下增加单主节点的伸缩性不会使用多主复制而是通过数据分区来解决。因为前者导致的复杂性已经超过了它能带来的好处不过在某些情况下也是可以采用多主复制的。 多数据中心的多主复制架构如下图所示 数据库的副本分散在多个数据中心在每个数据中心都有主库在每个数据中心内都是主从复制每个数据中心的写请求都会在本地数据中心处理然后同步到其他数据中心的主节点这样数据中心间的网络延迟对用户来说就变成了透明的这 意味着性能可能会更好对网络问题的容忍度更高多数据中心部署在不同的地理位置上对用户来说体验更好如果本地数据中心发生故障能够将请求转移到其他数据中心等本地数据中心恢复并复制赶上进度后能继续提供服务。
3.1 多主复制的应用场景
断网后仍继续工作的应用程序
如果你使用的手机和电脑是同一个生态的话那么一般情况下备忘录内容的修改能在设备之间进行同步。从架构的角度来看每个设备都相当于是一个数据中心每个数据中心都能进行写入它符合多主复制模型。数据中心间的网络是极度不可靠的当手机离线在电脑端对备忘录进行修改后那么当手机再接入互联网需要完成设备间的数据同步这就是异步多主复制的过程。 在线协同文档
当有用户对文档进行编辑时所做的更改将立即被异步复制到服务器和其他任何正在使用该文档的用户每个用户操作的文档都相当于是一个数据中心这种情况与我们上文所述的在离线设备上对备忘录进行修改有相似之处。不过在这种情况下为了加速协同和提高文档的使用体验需要解决同时编辑产生的写入冲突问题。
3.2 解决写入冲突
虽然我们在上文中提到了多主复制能带来诸多好处多主带来的伸缩性、更好的容错机制和减少地理位置造成的延时但是相伴的 配置复杂 和 写入冲突问题 也是需要我们直面的。
如下图所示用户1修改标题为B用户2修改标题为C那么此时就会发生写入冲突我们很难说得清楚将谁的结果指定为最终修改结果是合适的但是我们还是不得不将多主数据库的值收敛至一致的状态。 最后写入胜利LWWlast write wins 是比较常用的方法我们可以为每个请求增加时间戳或者唯一的ID挑选其中较大的值作为最终结果并将其他的值丢弃不过这种情况容易造成数据丢失比如在分布式服务中存在的 不可靠的时钟 问题可能后写入的值反而携带的时间戳更靠前那么这种情况下就会将我们预期被写入的结果丢弃。
另一种方法是可以为每个主库分配一个ID编号具有更高的ID编号的主库具有更高的优先级但是这也会产生数据丢失问题。
如果不想发生数据丢失可以以某种组合的方式将这些值组合在一起。以上图中对标题的修改为例可以将标题修改结果拼接成 B/C不过这种情况需要用户对结果进行修正。和该方式类似的还可以考虑将所有对数据修改的冲突都显示的记录下来之后提示用户进行修改。
版本向量 也是一种解决冲突的方式。以缓存为例我们为每个键维护一个版本号每次写入时先进行读取并且必须将之前读取的所有值合并在一起其中删除的值会被标记墓碑这样就能够避免在合并完成后仍然出现曾删掉的值。在写入完成后版本号递增将新版本号与写入的值一起存储。在多个副本并发接受写入时每个副本也需要维护版本号每个副本在处理写入时增加自己的版本号。所有副本的版本号集合称为 版本向量版本向量会随着读取和写入在客户端和服务端之前来回传递并且允许数据库区分覆盖写入和并发写入。版本向量能够 确保从一个副本读取并随后写回到另一个副本是安全的。
不过虽然我们介绍了这么多解决冲突的方式但是实际上 避免冲突 是最好的方式。比如我们可以确保特定记录的所有写入都通过同一个主库那么就不会发生冲突了。 关于并发的理解如果是在单体服务中我们可以通过时间戳来判断两个事件同时发生如果是在分布式系统中因为分布式系统存在不可靠的时钟问题所以在实际的系统中很难判断两个事件是否是同时发生所以并发在 字面时间上的重叠并不重要。实际上并发强调的是 两个事件是否能意识到对方的存在如果都意识不到对方的存在即两个事件都不在另一个之前发生那么这两个事件是并发的那么它们存在需要被解决的 并发写入 冲突。 5. 无主复制
无主复制与单主、多主复制采用不同的复制机制它没有主库和从库的职责差异而是放弃了主库的概念每一个数据库节点都可以处理写入请求因此它适用于 高可用、低延时、且能够容忍偶尔读到陈旧值 的应用场景。
这种复制模式还有一个好处是不存在故障转移当某个节点宕机时应用会将该请求转发到其他正常工作的节点。等到宕机节点重新连接之后该节点可以通过以下两种方式赶上错过的写入
读修复适用于读频繁的值客户端并行获取多个节点时如果它检测到陈旧的值那么将读取到的新值把陈旧的值覆盖掉反熵开启后台进程该进程不断查找副本之间的数据差异并将任何缺少的数据从一个副本复制到另一个副本
无主复制的每个数据库节点都能处理读写请求但是并不是在某单个节点写入完成后就被认定为写入成功或在单个节点读取就认为该值是读取结果。它的读写遵循 法定人数原则与zookeeper处理写入请求使用的容错共识算法类似。 一般地说如果有n个副本每个写入必须由 w 个节点确认才能被认为是成功的并且每个读取必须查询 r 个节点。只要 w r n我们可以预期在读取时获得最新的值因为在 r 个读取中至少有一个节点是最新的遵循这些 r 值和 w 值的读写被称为法定人数读写。常见的配置是将n节点数配置成奇数并设置 w r (n 1) / 2 向上取整这样保证了写入和读取的节点集合必然有重叠所以读取的节点中必然至少有一个节点具有最新的值。 如下图所示用户1234会将写入请求发送到所有的3个数据库副本并且在其中两个副本返回成功时即认为写入成功而忽略了宕机副本错过写入的事实用户2345在读取数据时也会将请求发送到所有副本并将其中最新的值看作读取的结果。 每种复制的模式都有优点和缺点单主复制是比较流行的它容易理解而且无需处理冲突问题写入只有主节点处理。不过在节点故障或者网络出现较大的延时时多主复制和无主复制可以更加健壮但是它们只能提供较弱的一致性保证。
转载自https://juejin.cn/post/7431007636046839817