分布式服务高可用实现:复制
分布式服务高可用实现:复制
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,那么此时就会发生写入冲突,我们很难说得清楚将谁的结果指定为最终修改结果是合适的,但是我们还是不得不将多主数据库的值收敛至一致的状态。
最后写入胜利(LWW,last 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
相关文章:

分布式服务高可用实现:复制
分布式服务高可用实现:复制 1. 为什么需要复制 我们可以考虑如下问题: 当数据量、读取或写入负载已经超过了当前服务器的处理能力,如何实现负载均衡?希望在单台服务器出现故障时仍能继续工作,这该如何实现ÿ…...

基于yolov8、yolov5的车型检测识别系统(含UI界面、训练好的模型、Python代码、数据集)
摘要:车型识别在交通管理、智能监控和车辆管理中起着至关重要的作用,不仅能帮助相关部门快速识别车辆类型,还为自动化交通监控提供了可靠的数据支撑。本文介绍了一款基于YOLOv8、YOLOv5等深度学习框架的车型识别模型,该模型使用了…...

机器学习—决定下一步做什么
现在已经看到了很多不同的学习算法,包括线性回归、逻辑回归甚至深度学习或神经网络。 关于如何构建机器学习系统的一些建议 假设你已经实现了正则化线性回归来预测房价,所以你有通常的学习算法的成本函数平方误差加上这个正则化项,但是如果…...

Java Optional详解:避免空指针异常的优雅方式
在 Java 编程中,空指针异常(NullPointerException)一直是困扰开发者的常见问题之一。为了更安全、优雅地处理可能为空的值,Java 8 引入了 Optional 类。Optional 提供了一种函数式的方式来表示一个值可能存在或不存在,…...

SpringBoot开发——整合EasyExcel实现百万级数据导入导出功能
文章目录 一、EasyExcel 框架及特性介绍二、实现步骤1、项目创建及依赖配置(pom.xml)2、项目文件结构3、配置文件(application.yml)4、启动类 Application.java5、配置类 EasyExcelConfig.java6、服务接口定义及实现 ExcelService.java7、控制器类 ExcelController.java8、…...

AcWing 1097 池塘计数 flood fill bfs搜索
代码 #include <bits/stdc.h> using namespace std;const int N 1010, M N * N;typedef pair<int, int> PII;int n, m;char g[N][N]; bool st[N][N]; PII q[M];void bfs (int xx, int yy) {int hh 0, tt -1;q[ tt] {xx, yy};st[xx][yy] true;while (hh <…...

R门 - rust第一课陈天 -内存知识学习笔记
内存 #mermaid-svg-1NFTUW33mcI2cBGB {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-1NFTUW33mcI2cBGB .error-icon{fill:#552222;}#mermaid-svg-1NFTUW33mcI2cBGB .error-text{fill:#552222;stroke:#552222;}#merm…...

java itext后端生成pdf导出
public CustomApiResult<String> exportPdf(HttpServletRequest request, HttpServletResponse response) throws IOException {// 防止日志记录获取session异常request.getSession();// 设置编码格式response.setContentType("application/pdf;charsetUTF-8")…...

信号-3-信号处理
main 信号捕捉的操作 sigaction struct sigaction OS不允许信号处理方法进行嵌套:某一个信号正在被处理时,OS会自动block改信号,之后会自动恢复 同理,sigaction.sa_mask 为捕捉指定信号后临时屏蔽的表 pending什么时候清零&…...

38配置管理工具(如Ansible、Puppet、Chef)
每天五分钟学Linux | 第三十八课:配置管理工具(如Ansible、Puppet、Chef) 大家好!欢迎再次来到我们的“每天五分钟学Linux”系列教程。在前面的课程中,我们学习了如何安装和配置邮件服务器。今天,我们将探…...

网络技术-定义配置ACL规则的语法和命令
定义ACL(访问控制列表)规则时,具体命令会根据所使用的设备和操作系统而有所不同。以下是一些常见的设备和操作系统中定义ACL规则的命令示例: 一:思科(Cisco)路由器/交换机 在思科设备中&#…...

动态规划一>子数组系列
题目: 2.解析: 代码: public int maxSubArray(int[] nums) {int n nums.length;int[] dp new int[n 1];int ret Integer.MIN_VALUE;for(int i 1; i < n; i){dp[i] Math.max(nums[i - 1], dp[i - 1] nums[i - 1]);ret Math.max(…...

一觉睡醒,全世界计算机水平下降100倍,而我却精通C语言——scanf函数
大家好啊,我是小象٩(๑ω๑)۶ 我的博客:Xiao Fei Xiangζั͡ޓއއ 很高兴见到大家,希望能够和大家一起交流学习,共同进步。* 这一节我们主要来学习scanf的基本用法,了解scanf返回值,懂得scanf占位符和…...

Altium Designer使用技巧(五)
一、敷铜(快捷键T-G-A) 1、工具栏点“设计”—>“规则”。 可以修改覆铜连线的宽度,也可以选择“直接连接”使得铜和网络节点完全相连。 这里方式有三种,可根据需要各个人习惯去设置。 2、敷铜与线全链接 (1)少量的话可右键“…...

Docker 的安装与使用
Docker 的安装 Docker 是一个开源的商业产品,有两个版本:社区版(Community Edition,缩写为 CE)和企业版(Enterprise Edition,缩写为 EE)。 Docker CE 的安装请参考官方文档…...
Android Studio 中三方库依赖无法找到的解决方案
目录 错误信息解析 解决方案 1. 检查依赖版本 2. 检查 Maven 仓库配置 3. 强制刷新 Gradle 缓存 4. 检查网络连接 5. 手动下载依赖 总结 相关推荐 最近,我在编译一个 Android 老项目时遇到了一个问题,错误信息显示无法找到 com.gyf.immersionba…...

PGMP练-DAY24
DAY241A program has completed and closed. The training for the receiving organization has also delivered. But the stakeholders still concern that the benefits cannot be realized in long term.What does the program manager review to improve the situation?项…...

【C++动态规划 最长公共子序列】1035. 不相交的线|1805
本文涉及知识点 C动态规划 LeetCode1035. 不相交的线 在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。 现在,可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线,这些直线需要同时满足: nums1[i] nums2[j] 且绘制的…...

FFmpeg的基本结构
FFmpeg框架可以简单分为两层,上层是以ffmpeg、ffplay、ffprobe为代表的命令行工具;其底层支撑是一些基础库,包含AVFormat、AVCodec、AVFilter、AVDevices、AVUtils等模块库。 常用函数如下: 1. AVFormat 封装/解封装模块 avf…...

react 受控组件和非受控组件
在 React 中,受控组件和非受控组件是两种处理表单元素(如输入框、选择框等)值的方式。 1. 受控组件 受控组件是指 React 组件的表单元素的值是由 React 组件的 state 来管理的。换句话说,React 会全程控制表单元素的值ÿ…...

C语言模块化概述
一、函数名的意义 1.c语言是一门面向过程的语言:所谓的过程就是动词,动作。 功能块动词1动词2……动词 2.功能块:就是一堆动词(动作)的组合,动作通过函数来实现。 3.函数的功能:承上启下 承…...

WPF 中的视觉层和逻辑层有什么区别?
在 WPF(Windows Presentation Foundation)中,视觉层和逻辑层是两个不同的概念,它们分别涉及到界面的展示和应用的行为。要理解这两个层次的区别,我们需要从 WPF 的设计背景、架构以及它们之间的相互关系来全面分析。 …...

Kafka简单实践
使用 Apache Kafka 和 Swoole 的 PHP 实践案例 一、引言 Apache Kafka 是一个开源的分布式流处理平台,能够处理大量的实时数据流。由于其高吞吐量、可扩展性和持久性,Kafka 成为构建微服务架构和大数据处理的重要工具。Swoole 是一个高性能的异步网络通…...

JS
文章目录 项目地址一、JS1.1 if语句1.2 for循环1.2 三元表达式1.3 switch1.4 数组的push方法1.5 fuction1.5.1 arguments1.6 匿名函数1.7 预解析1.8 js对象1.8.1创建一个类1.8.2 遍历对象1.9 js的内置对象1.9.1 随机整数二、DOM2.1 获取元素2.2 事件基础2.2.1 事件三要素2.2.2 …...

【原创】java+ssm+mysql商品库存管理系统(进销存)设计与实现
个人主页:程序猿小小杨 个人简介:从事开发多年,Java、Php、Python、前端开发均有涉猎 博客内容:Java项目实战、项目演示、技术分享 文末有作者名片,希望和大家一起共同进步,你只管努力,剩下的交…...

three.js 杂记
欧拉角旋转变换 x,y,z 弧度单位 THREE.MathUtils.DEG2RAD 度数转弧度 new THREE.Euler( - 90 * THREE.MathUtils.DEG2RAD, 0, 0 ) radius:半径 setFromSphericalCoords ( radius : Float, phi : Float, theta : Float ) : this 从球坐标中的radius、phi和theta设置该向量…...

基于Hadoop、hive的数仓搭建实践
文章目录 架构图Hadoop搭建Hive 搭建MySQL搭建官网文档下载配置配置hive环境变量配置日志文件配置hive-site 复制mysql 驱动包删除日志包初始化元数据启动metastore服务使用hive CLI启动hiveServer2访问hiveserver2客户端连接beeline shell连接 Dbeaver连接经验 基于HDFS Hive…...

新的恶意软件活动通过游戏应用程序瞄准 Windows 用户
一种新的恶意软件 Winos4.0 被积极用于网络攻击活动。FortiGuard实验室发现,这种先进的恶意框架是从臭名昭著的 Gh0strat 演变而来的,配备了模块化组件,可在受感染的设备上进行一系列恶意活动。 这些攻击已在游戏相关应用程序中发现…...

【Hutool系列】反射工具-ReflectUtil
前言 反射是 Java 中一种强大的机制,可以在运行时动态地获取类的信息并操作类的属性和方法。在 Java 中,通过反射可以获取和设置类的字段、调用类的方法、创建类的实例等。Java的反射机制,可以让语言变得更加灵活,对对象的操作也更…...
【操作系统专业课】第二次作业
第1题(进程同步与互斥) 使用二值信号量实现 n 个进程之间的互斥。 1. 定义一个二值信号量 mutex= 1。 二值信号量:二值信号量只有两种取值,0 (资源已被占用)和 1(资源可用)。 2. 进程进入临界区前的操作:每个进程在进入临界区之前,都需要执行 P(mutex) 操作。 P 操作…...