分布式系统一致性与共识算法

news2024/11/25 12:39:34

        分布式系统的一致性是指从系统外部读取系统内部的数据时,在一定约束条件下相同,即数据(元数据,日志数据等等)变动在系统内部各节点应该是一致的。

        一致性模型分为如下几种:

① 强一致性   所有用户在任意时刻读到的数据,无论会请求到哪个节点上,都是一致的;

② 单调一致性  有用户在任意时刻读到的数据一定会比之前读到的数据新,也就是说,可获取的数据顺序必是单调递增的;

③ 弱一致性   相比于强一致性,弱一致性要求没那个严格,不同用户在不同时间读到的数据可能是不一致的,但过了一段时间会达到一致。

④ 最终一致性    最终一致性属于弱一致性的一种实例。用户只能读到某次更新后的值,但系统保证数据将最终达到完全一致的状态,只是所需时间不能保障。

        实际上一致性还包括顺序一致性、读写一致性、因果一致性等等,这里不做过多讨论。

        之前在分布式基础的文章里也有过讨论,对于分布式系统,主要有CAP理论和BASE理论。对于现实的网络来说,BASE理论是比较适用的,即实现最终一致性。

        再说下共识,共识是指不同的节点通过某种方法对于某种状态达成一致的过程。分布式一致性强调了多个副本呈现的数据的状态,共识强调多个节点之间对某个提案达成一致的过程。通过共识,实现某种一致性,这种一致性包括某提案的一致性,如选举,下线通知等等,也包括数据的一致性。

        共识算法目前有很多种。但根据不同的错误类型可以分为两大类算法:

        1、拜占庭问题。恶意伪造导致的错误;

        2、非拜占庭。出现故障非恶意伪造。

拜占庭问题:

我觉得一个比较简单明了的解释:

        假设有9位将军投票,其中1名叛徒。8名忠诚的将军中出现了4人投进攻,4人投撤离的情况。这时候叛徒可能故意给4名投进攻的将领送信表示投票进攻,而给4名投撤离的将领送信表示投撤离。这样一来在4名投进攻的将领看来,投票结果是5人投进攻,从而发起进攻;而在4名投撤离的将军看来则是5人投撤离。这样各支军队的一致协同就遭到了破坏。

        目前存在的算法主要包括PAXOS,RAFT,还有用于区块链的工作量证明、权益证明等等算法。PAXOS,RAFT主要用来解决不存在拜占庭问题的情况,即不存在作弊情况,基本上都是网络抖动,网络出错,系统宕机等问题,因此基本上都是用于系统内部自治,像开源的各种中间件,消息队列,Redis集群等等。而工作量证明,权益证明这些用于区块链等领域的算法就需要考虑拜占庭问题。

共识算法:

  1. Gossip算法;
  2. Raft算法;
  3. ZAB;
  4. ES选举算法;

一、Gossip算法(论文地址:Gossip论文)

        我觉得用一个简单例子来描述gossip比较合适:说一个村里儿,有一家突然有一天发生了一个事儿,女儿嫁给了一个大她20岁的老爷们儿,她邻居先听说了这个事儿,火急火燎地就把这个事儿告诉了和她打麻将的几个人儿,然后这几个人又分别告诉了好朋友,同时第一听说这件事儿的邻居还没闲下来,还再巴巴地和别人说,就这样很快,整个村儿都知道了。一个消息最后所有人都知道了。

        Gossip协议就是这样一种,没有中心节点,由最初的一个节点先随机选择几个节点通信,然后知道信息的节点同样再选择几个节点通信,就这样看似杂乱无章地相互通信,最终达到了一致性的一种算法。

        这种算法的一个优势是不管是有新节点加入,还是有节点宕机,对现有的网络和在线节点都不会有任何的影响。就算是突然有个新节点加入,也会达到最终一致性。

        目前这个算法已经非常成熟了。Redis-Cluster目前采用了这种协议。RedisCluster集群的每个节点都会有两个重要的数据结构,一个是clusterNode,记录了当前节点的状态,一个是clusterState,包含整个集群的状态等信息。clusterState的slots数组存储的是所有槽分配的地址信息,注意和clsuterNode的区别。clusterNode的slots数组记录的就是当前节点的槽分配信息。如果我们想要查某个slot在哪个节点上,直接使用clusterState的slot记录,时间复杂度是O(1)。

在RedisCluster中有几个比较重要的通信类别:

  • Meet 通过,当前集群的节点会向新节点发送握手请求,加入现有集群。
  • Ping 节点每秒会向集群中其他节点发送 ping 消息,消息中带有自己已知的两个节点的地址、槽、状态信息、最后一次通信时间等。
  • Pong 节点收到 ping 消息后会回复 pong 消息,消息中同样带有自己已知的两个节点信息。
  • Fail 节点 ping 不通某节点后,会向集群所有节点广播该节点挂掉的消息。其他节点收到消息后标记已下线。

        Gossip的特点就是随机冗余发送,最终达到了一致性。但Gossip协议本身也是存在很多的缺点,比如就是通信的冗余,因为就算一个节点收到了某个消息,那么下次仍然有可能收到的,这样就造成了资源的浪费。

        RedisCluster的机制是会每100ms发送一次 ping,这里也不是发送给集群中的所有节点,而是随机选择部分节点。一个clusterSendPing会带上当前节点以及其他部分节点的信息发送出去。

        其实上面说的clusterSendPing也不能完全保证每个节点都能收到其他节点的消息。因此集群还会每1秒都会发送随机选择5个节点,并从中选择最久没有接受pong消息的节点,发送消息。

        此外,还会遍历当前节点中存储的所有其他节点,选择一个最近接受ping消息时间已经超过了cluster_node_timeout/2的就会发送消息。

        上面是RedisCLuster使用Gossip来达到最终一致性的。除此之外在选举过程Redis采用的是Raft算法。

二、Raft算法

        Raft算法是基于之前的PAXOS算法优化的一种可行性方案,PAXOS本身比较复杂,本文也不做赘述。Raft目前是很多成熟的分布式系统都采用的算法。如Redis-cluster,Nacos,Rocketmq,以及Kafka2.8开始摘除ZK之后,也采用基于Raft的共识机制。

        Raft算法大的策略是将问题分治处理,将整个流程分为选举、日志同步、安全性、日志压缩、成员变更等等。

        该算法的前提是为系统定义了三个角色:

        1、Leader角色。整个系统只有该角色负责处理客户端的请求,并把日志同步给Follower(也是一个角色);

        2、Follower角色。一个系统会有多个Follower,他们会接收Leader的日志,并在Leader通知提交日志的时侯进行提交(持久化)。它只能接收请求;

        3、Candidate角色。该角色只有在进行Leader选举的时侯才会存在,每一个Follower都可能临时成为 Candidate 

        一个系统在正常运转的时侯,肯定只有一个Leader和多个Follower存在。        

下面是一个示意图,我觉得它描述得非常清晰(图片非原创)。

        如果一个Follower超过一定时间没有收到Leader角色的心跳,那么它就会等待一段随机的时间发起Leader选举,并将自己也设置为Candidate角色,且投自己一票(当然,你说要投自己一票,让别人也投你,你应该带上自己的最后一条日志的term和log index)。

        如果在超时之前获得了大多数的投票,那么自己就变成Leader;如果收到了其他Leader的消息,证明已经有新的Leader,自己还变成Follower;如果没有获得超过一半的投票,那么就等待该次选举周期超时后再发起选举。

        每一次选举的周期被称为term,随着选举的不断发生,term不断增大。而Candidate在选举时只选举term更大,index最新的节点。这个周期是非常重要的,他要确保同一个参与选举的角色在一个Term内只能投一票,避免会出现脑裂的情况。

        Redis-Cluster采用的就是基于Raft的选举,当某个Master挂掉之后,多个Slave会在间隔一段随机时间后发出广播消息,希望其他Master投它一票,注意这里的随机时间,避免同时发起选举,导致长时间选不出一个master来,不过这样也不能保证发起选举就在一个周期内就会选举成功,这里导致一个问题是,RedisCluster本身也是CP模式的,Master到Slave数据同步是异步的,可能会出现数据丢失的情况。

Raft日志同步:

        Raft算法除了可以用在上面的master选举意外,还可以用于日志同步。

       思想: Leader接收客户端的请求后,会并行地向所有Follower以日志的形式发送,当有半数以上的Follower成功提交了日志,那么才会告知客户端成功提交了。每个日志都会包括term和索引index,以及真正要执行的指令。

        那么Follower是如何处理Leader发来的日志呢?

        首先Follower会将index-1的内容和Leader的进行对比,如果一致,就返回true.如果不等,就返回false,Leader收到false,会减小index值,重新发送,直到最后内容相同,Follower之后的内容都会被覆盖。

        到此为止,可以看到Raft为每个日志都分配了term和index保证唯一性,此外,它也强制Follower必须要和当前的Leader保持一致。

官方描述:

  • leader为每个follower维护一个 nextIndex ,表明下一个将要发送给follower的log entry
  • 当leader刚上任时,会把所有的 nextIndex 设置成其最后一个log entry的index加1,如上图,则是11
  • 当follower的日志和leader不一致时,一致性检查会失败,那么会把 nextIndex 减1
  • 最终 nextIndex 会是leader和follower相同log entry的index加1,这时候,再发送 AppendEntries 会成功,并且会把follower的所有之后不一致的日志删除掉。

Nacos的CP模式用的就是Raft算法,不过其是用了阿里改造的SOFA-Jraft。

Raft文章推荐: Raft日志复制 Raft论文

三、ZAB协议

        该协议是在zookeeper中应用的一种一致性协议,它有很多和Raft相似的地方。比如都是由leader发起写操作,都采用心跳来检测存活性,在选举过程中也都采用谁先获得多数投票谁就成为Leader。但ZAB又有很多独特的地方。如ZAB的Observer不参与请求,只提供给客户端的读请求。

        ZAB全称是Zookeeper Atomic Broadcast,zookeeper原子广播协议,字面意思就可知该协议是基于广播进行的。

        Zookeeper主要有两种模式,崩溃恢复和广播模式,它就是在这两种模式下切换。

        崩溃恢复和Raft的选举过程也很类似,也是在Leader崩溃之后,Follower选择出一个新的Leader(也是获得超过一半的投票)。但投票条件不同,ZAB要求Follower在投票给一个节点前,必须和该节点的日志保证一致,而Raft没这要求,谁term高就给谁投。ZAB的投票参考因素是ZXID,是一个事务的序号,是不断递增的一个值。ZXID是一个64位数字,高32位表示每一代Leader(epoch),低32位表示事务的ID(count)。

        那么可能问了,如果Leader在提交事务后崩溃了或者在把日志复制给Follower时,新Leader会继续处理吗?针对该问题,ZAB协议保证了两点:

        1、已经被Leader的事务会被提交;

        2、已经被Leader跳过的事务不会再执行;

        日志复制和Raft处理方式也是大体一样,也是收到半数以上Follower的Ack后,才会提交事务。区别是ZAB中引入了一个队列,并将带有ZXID的日志按顺序发送给所有Follower,相比于Raft,ZAB实现了解耦。

广播模式的具体流程:

        1、不论是zk集群的哪个机器收到了写请求,就算是follower收到了,它首先是将请求转发给leader服务器,leader负责具体的写请求处理;

        2、leader生成proposal提案,并生成一个全局的ZXID,在后续的恢复模式中是很重要的一个标识。然后通过与每个follwer的队列(FIFO)发送给follower;ZXID在上面已经提到了,官方的对其的定义:

  • Every change to the ZooKeeper state receives a stamp in the form of a zxid (ZooKeeper Transaction Id). This exposes the total ordering of all changes to ZooKeeper. Each change will have a unique zxid and if zxid1 is smaller than zxid2 then zxid1 happened before zxid2.

        3、follower收到消息后,会将数据持久化到硬盘,随后向leader发送一个ACK;

        4、当leader收到集群一半以上的follower发送的ACK,就会向所有的follower发送commit;

        5、follower收到leader发送的commit消息后,就将数据载入内存,供客户端读取。

下面这张图(非原创)比较清晰描述了上个过程,但该图有个问题,就是请求不一定就是发送给leader,可以发送给ZK集群任意一个节点,只不过是如果请求是写请求的话,follower会转发给leader。

其实上面也是一个分布式事务的实现,通过三阶段提交实现事务。关于分布式事务的介绍可以参见: 分布式事务

四、ES选举Bully算法

        ES的选举并没有采用常用的Raft算法,而是使用Bully算法。其基础是为集群的每个节点都分配一个唯一id。ES会为整个集群的每个node分配一个唯一的id,这个id是node在启动时随机分配的。此外,每个节点都会有一个版本号clusterVersion,该字段代表的是状态值,master节点一定是拥有最新版本号的节点。

        ES里的节点分成Master节点,Master候选节点和数据节点。角色需要通过配置来做。Master节点对应Raft的Leader,Master候选节点也是对应Follower和Candidate。

        那么当发现Master节点下线时,候选节点投票的原则是首先选择版本号更高的,如果版本号都相同,就选择id最小的。和Raft相同的是,同样是要求一半以上投票的节点才可成为Master节点,但和Raft不同的是,ES并没有完全避免脑裂的问题,这也是ES选举过程存在的最大问题,之所以会出现脑裂是因为虽然也是获得绝大多数投票的才能成为master,但是他并没有限制一个节点只能投一票,如果一个节点在投票后迟迟等不到选举的结果,可能又开始投票,此时因为优先级的问题又投票给其他节点了,就意味着这个节点投了两票给不同节点,就会出现问题。这种问题Raft算法解决的就很好,Raft引入了Term的概念,一个Term周期内,一个节点只能投一票。Term大的优先级高。    

参考资料:

zookeeper面试题

Raft协议和Zab协议及其对比

分布式一致性与共识算法

Redis-Cluster实现

Raft算法详解

共识机制

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1463428.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue源码分析之nextTick源码分析-逐行逐析-错误分析

nextTick的使用背景 在vue项目中,经常会使用到nextTick这个api,一直在猜想其是怎么实现的,今天有幸研读了下,虽然源码又些许问题,但仍值得借鉴 核心源码解析 判断当前环境使用最合适的API并保存函数 promise 判断…

【RL】Actor-Critic Methods

Lecture 10: Actor-Critic Methods The simplest actor-critic (QAC) 回顾 policy 梯度的概念: 1、标量指标 J ( θ ) J(\theta) J(θ),可以是 v ˉ π \bar{v}_{\pi} vˉπ​ 或 r ˉ π \bar{r}_{\pi} rˉπ​。 2、最大化 J ( θ ) J(\theta)…

计算机服务器中了DevicData勒索病毒怎么办?DevicData勒索病毒解密数据恢复

网络技术的发展与更新为企业提供了极大便利,让越来越多的企业走向了正规化、数字化,因此,企业的数据安全也成为了大家关心的主要话题,但网络是一把双刃剑,即便企业做好了安全防护,依旧会给企业的数据安全带…

Prometheus+Grafana 监控

第1章Prometheus 入门 Prometheus 受启发于 Google 的 Brogmon 监控系统(相似的 Kubernetes 是从 Google的 Brog 系统演变而来),从 2012 年开始由前 Google 工程师在 Soundcloud 以开源软件的 形式进行研发,并且于 2015 年早期对…

如何在Linux搭建Inis网站,并发布至公网实现远程访问【内网穿透】

如何在Linux搭建Inis网站,并发布至公网实现远程访问【内网穿透】 前言1. Inis博客网站搭建1.1. Inis博客网站下载和安装1.2 Inis博客网站测试1.3 cpolar的安装和注册 2. 本地网页发布2.1 Cpolar临时数据隧道2.2 Cpolar稳定隧道(云端设置)2.3.…

论文阅读:How Do Neural Networks See Depth in Single Images?

是由Technische Universiteit Delft(代尔夫特理工大学)发表于ICCV,2019。这篇文章的研究内容很有趣,没有关注如何提升深度网络的性能,而是关注单目深度估计的工作机理。 What they find? 所有的网络都忽略了物体的实际大小,而关注他们的垂直…

全球最强开源大模型一夜易主!谷歌Gemma 7B碾压Llama 2 13B,今夜重燃开源之战

一声炸雷深夜炸响,谷歌居然也开源LLM了?! 这次,重磅开源的Gemma有2B和7B两种规模,并且采用了与Gemini相同的研究和技术构建。 有了Gemini同源技术的加持,Gemma不仅在相同的规模下实现SOTA的性能。 而且更令…

嵌入式学习-qt-Day3

嵌入式学习-qt-Day3 一、思维导图 二、作业 完善对话框,点击登录对话框,如果账号和密码匹配,则弹出信息对话框,给出提示”登录成功“,提供一个Ok按钮,用户点击Ok后,关闭登录界面,跳…

Transformer 架构—Encoder-Decoder

文章目录 前言 一、Encoder 家族 1. BERT 2. DistilBERT 3. RoBERTa 4. XML 5. XML-RoBERTa 6. ALBERT 7. ELECTRA 8. DeBERTa 二、Decoder 家族 1. GPT 2. GPT-2 3. CTRL 4. GPT-3 5. GPT-Neo / GPT-J-6B 三、Encoder-Decoder 家族 1. T5 2. BART 3. M2M-100 4. BigBird 前言 …

SpringBoot---集成MybatisPlus

介绍 使用SpringBoot集成MybatisPlus框架。 第一步&#xff1a;添加MybatisPlus依赖 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.4</version> </dependenc…

MIT6.S081学习——一、环境搭建、资料搜集

MIT6.S081学习——一、环境搭建、资料搜集 1、环境准备2、资料搜集2、环境搭建2.1 Linux环境准备2.2 环境搭建2.2.1 根据官网指导代码进行相关工具的安装2.2.2 下载并且编译QEMU 3、VSCode远程连接Ubuntu3.1 安装remote-ssh3.1.1 安装插件3.1.2 配置config文件 3.2 Ubuntu安装S…

springcloud:2.OpenFeign 详细讲解

OpenFeign 是一个基于 Netflix 的 Feign 库进行扩展的工具,它简化了开发人员在微服务架构中进行服务间通信的流程,使得编写和维护 RESTful API 客户端变得更加简单和高效。作为一种声明式的 HTTP 客户端,OpenFeign 提供了直观的注解驱动方式,使得开发人员可以轻松定义和调用…

Redis突现拒绝连接问题处理总结

一、问题回顾 项目突然报异常 [INFO] 2024-02-20 10:09:43.116 i.l.core.protocol.ConnectionWatchdog [171]: Reconnecting, last destination was 192.168.0.231:6379 [WARN] 2024-02-20 10:09:43.120 i.l.core.protocol.ConnectionWatchdog [151]: Cannot reconnect…

win32 汇编读文件

做了2个小程序&#xff0c;没有读成功&#xff1b;文件打开了&#xff1b; .386.model flat, stdcalloption casemap :noneinclude windows.inc include user32.inc includelib user32.lib include kernel32.inc includelib kernel32.lib include Comdlg32.inc includelib …

Pormise---如何解决javascript中回调的信任问题?【详解】

如果阅读有疑问的话&#xff0c;欢迎评论或私信&#xff01;&#xff01; 本人会很热心的阐述自己的想法&#xff01;谢谢&#xff01;&#xff01;&#xff01; 文章目录 回调中的信任问题回调给我们带来的烦恼&#xff1f;调用过早调用过晚调用的次数太少或太多调用回调时未能…

数据结构之链表经典算法QJ题目

目录 单链表经典算法题目1. 单链表相关经典算法OJ题&#xff1a;移除链表元素思路一&#xff1a;思路二&#xff1a; 2. 单链表相关经典算法QI题&#xff1a;链表的中间节点思路一思路二 3. 单链表相关经典算法QJ题&#xff1a;反转链表思路一思路二 4. 单链表相关经典算法QJ题…

pytest 框架自动化测试

随笔记录 目录 1. 安装 2. 安装pytest 相关插件 2.1 准备阶段 2.2 安装 2.3 验证安装成功 3. pytest测试用例的运行方式 3.1 主函数模式 3.1.1 主函数执行指定文件 3.1.2 主函数执行指定模块 3.1.3 主函数执行某个文件中的某个类、方法、函数 3.1.4 主函数执行生…

学习鸿蒙背后的价值?星河版开放如何学习?

现在是2024年&#xff0c;华为在1月18开展了鸿蒙千帆起仪式发布会。宣布了鸿蒙星河版&#xff0c;并对开发者开放申请&#xff0c;此次发布会主要是说明了&#xff0c;鸿蒙已经是全栈自研底座&#xff0c;鸿蒙星河版本的编程语言改为ArkTS/仓颉&#xff0c;内核改为鸿蒙原生内核…

操作系统虚拟内存(上)

一、虚拟内存简要 1.为什么需要虚拟内存? 如果第一个程序在 2000 的位置写入一个新的值&#xff0c;将会擦掉第二个程序存放在相同位置上的所有内容&#xff0c;所以同时运行两个程序是根本行不通的&#xff0c;这两个程序会立刻崩溃&#xff0c;所以想要在这种情况下&#…

SQL 练习题目(入门级)

今天发现了一个练习SQL的网站--牛客网。里面题目挺多的&#xff0c;按照入门、简单、中等、困难进行了分类&#xff0c;可以直接在线输入SQL语句验证是否正确&#xff0c;并且提供了测试表的创建语句&#xff0c;也可以方便自己拓展练习&#xff0c;感觉还是很不错的一个网站&a…