0

本文讲分布式的共识概念,算法,应用。涉及到raft,paxos(basic,multi,fast),zk,etcd,chubby。以及思考。关注与如何共识,对于zk,etcd,chubby的接口如何应用于服务发现,分布式锁等略过。

共识:一个或多个节点提出提案,算法选择一个值。一致同意,完整(只决定一次),有效(节点投票是有效值,是被提议的值),终止(宕机不回来)。
paxos完全符合,但raft,zap考虑的是宕机还会回来的情况,用日志保证。能解决上篇(https://segmentfault.com/a/11...)中诸如以下问题:
全序广播相当于重复多伦共识:raft和zap等直接实现全序广播,mutil-paxos也是,都有leader,只决定值的顺序。有固定leader比每个节点写入,leader维持全序,其他节点同步,冲突少(若没有leader paxos那种要先同步一遍获取最大值,再投票,多了一次pre),画一个有领导和无领导每个带序号的图可以很好理解带leader会容易些,但单leader有瓶颈。
单领导类的共识:1选出一位领导者,2对领导者的提议进行表决(防止1,一个节点相信自己是领导)投票是同步的,动态成员扩展难,依靠超时检测节点失效,若只有一条特定网络不可靠,会进入领导频繁二人转局面。有领导后也不能领导决定,防止重新恢复等多领导定的情况。
缺点:要多数都同意,很慢。基本动态扩容很难,手动比如etcd

共识算法

raft

  • 共识过程
    数据一致性是通过日志复制的方式,client发给leader(写只发给leader,follower备份恢复用),leader写入日志,同步给follower,当多数follower写入日志并返回给leader时,leader提交数据,返回给客户端确认消息, 发给follower数据已提交,follower提交数据,发回确认给leader。所有的发送都随着跳频发过去。raft中所有server之间的通信都是RPC调用,并且只有两种类型的RPC调用:第一种是RequestVote,用于选举leader;第二种是AppendEntries。日志和投票结果都需要持续化写在磁盘中,保证宕机后重启任然正常。
    clipboard.png
  • leader选举
    leader(有任期字段term),candidate, follower.每个节点有在T到2T之间随机选择超时时间。leader和follower通过跳频联系。当一个follower收不到leader的跳频超时时将发起投自己的票。任何一个follower只能投一票,如果发现自己的日志比请求中携带的更新,则拒绝投票。当一轮投票结束有多个候选者时,这几个候选者重新分配随机的超时时间
  • leader同步日志给follower
    在上述数据共识中,当leader确认提交数据后,leader会一直不断地重试提交的rpc给follower、重试,直到请求成功;即使follower宕机了,重启后leader仍会接着发请求,直到请求成功,当leader宕机,如何向follower继续发;1.leader的日志只能增加,=》所以在选择时选term大,log长的 2.leader会把自己的log复制到其他机器,如果新达到多数并且此任期内已有数据过半(挂前的一次数据不会被重复提交)就提交,只提交新任期的,同步follower还是要同步。
  • log一致性
    每个日志entry:iterm+index.每次发送AppendEntries时需要带上一次的,follower检查是否一样,一样才接受来保证所有机器log一致
    leader重新选出,为了恢复log一致性,leader为集群中所有follower都保存一个状态变量,即nextIndex:1)nextIndex是leader准备向某个follower发送的下一个log entry的index;2)当leader刚刚即位后,nextIndex的初始值是(1+leader's last index);
    当leader看到请求被拒绝时,其动作非常简单:只需将nextIndex-1,再次尝试。
  • 需要持久化term和投票
    term需要存盘
    任意一个server在一个term内只能投出一票;一旦已经投给了一个candidate,它必须拒绝其他candidate的投票请求;其实server根本不在意把票投给谁,它只会把票投给最先到请求到它的candidate;为了保证这一点,必须把投票信息持久保存到磁盘上,这样可以保证即使该server投完票后宕机,稍后又立即重启了,也不会在同一个term内给第二个candidate投票了。

paxos

  • basic paxos
    第一阶段收集最新的N和V,第二阶段发起提议:
    clipboard.png
    实际上这里的proposal是leader。共识算法正常是proposor,leader,accepter,leaner(先忽略),用来决议proposer的提议号和是否成功的。每次proposal先到leader(可随机选取,不重要),leader发给accepter若没有冲突返回any否则返回已选的,继续上述过程。
    问题:多个Proposal可能出现死锁一直循环递增N的情况:

    clipboard.png

    上面这个是https://www.microsoft.com/en-...。为了方便理解,去除了实现细节。实时应用中,客户端不会自己处理冲突+1再次投票和发送给其他leaner,这些应该由另一个角色,在basic中,由一群c协调者,可以和acceptor一样,或者是其中的部分构成,每轮随机一个c作为leader,负责收集本轮结果和通知leaner。proposal->leader(每个client随机发就可以作为本轮leader)->pre->acceptors返回最大N的值V->带N请求->acceptors->leader->返回给proposal->client失败或者成功或再次投票->投票成功后发给leaner。此过程中CLIENT2再次发送是另一个leader。

  • fast paxos
    若proposal和acceptor,leader,leaner都是分布式,且要持久化,持久化+发送来回的代价就多了,
    若leader发现没有冲突,不再参与,proposal直接提交给acceptor(同一轮只投给先到的),直接发送给leaner,可以理解为基于乐观锁的思想,leaner和CLIENT都自行决议,
    若proposal没有决策成功(先到的就是投票,没有半数以上的),1.重新引入leader,异步发送给协调者,协调者选择(因为acceptor只投一次),发给proposal结果(再次引入leader)。2.无leader,在acceptor决议后发送给所有acceptor,其他acceptor收到此消息后对i+1轮的可以比较投票(即使同时刻一个一半也可以再比较投一次,这两种的比较都复杂,要看各个提议的acceptor集合,这部分看论文吧)。
    https://www.microsoft.com/en-...
  • muti-paxos
    当leader稳定,可以省去prepare阶段。简单的说用一个序号来标识是否leader稳定(和raft,zk是一样的,转共识为全序序号的过程),若稳定更新序号直接发送给acceptor,acceptor需要记录序号,若发现有index>当前则返回false重新prepare。因为没有prepare,不知道每次最大的n,不知道leader是否稳定加入了全序。在prepare时并不需要此过程。
    具体做法如下:

    clipboard.png

  chubby就是一个典型的Muti-Paxos算法应用,在Master稳定运行的情况下,只需要使用同一个编号来依次执行每一个Instance的Promise->Accept阶段处理。

raft/paxos/zap/mutli-paxos区别

  • raft要有一个leader。在选主时每个follower只能投一次,不成功随机时间下一次。有主时的共识由主来给日志编号,follower比较就好,follower保证稳定可替换即可。
  • mutli-paxos在去掉pre后和raft相似,都是日志记录,区别是mp允许任何acceptor升级为leader,raft则很严格比如只有最完整日志的才行,mp在preprare后会知道当前最大的index,对于旧的异步补空洞。raft觉得补空洞过程太繁琐,增加了选主的复杂度。
  • paxos leader不能那么重要(fast paxos在无冲突时甚至无leader参与),每次可以随机选,只是汇总投票。paxos在fast模式下,冲突处理时,每个acceptor可以更新选票重新投(其实是冲突的解决,也可以不算投票,根据集合等复杂的逻辑,在zk中就按照现有集合票数)。
  • zap还是有leader的。zap在无主的时候选举算法和fast paxos很像,有最大xid(类似pre阶段,只不过是上次存好的),先选主,每次选主的提案直接给acceptor并且采用无协调者的冲突处理。在有主时,用paxos的思想先pre收集并同步信息保证一致,主处理写,多数处理成功后回复。
  • paxos优势就是单主能不能抗住了,单主投票只能一次一个。

zookeeper

zk定位于分布式协调服务
官方:https://zookeeper.apache.org/...
下面介绍zk的常用功能,架构,共识过程。

架构

本身的数据组织以文件形式,每个叶节点znodes,非页节点只是命名空间的路径标识;但是存储于内存,记录磁盘日志,副本包含完整内存数据和日志,znodes维护节点的版本,zxid等所有信息。
Zookeeper对于每个节点QuorumPeer的设计相当的灵活,QuorumPeer主要包括四个组件:客户端请求接收器(ServerCnxnFactory)、数据引擎(ZKDatabase)、选举器(Election)、核心功能组件(Leader/Follower/Observer不同)


作用

1.单独zk集群元数据的可靠性和一致性保证,元数据保存在zk所有副本中(少量完全可以放在内存中数据)
路由,选择数据库,调度程序
2.单独zk集群,锁,防护令牌,获取锁或者zxid
3.变更通知,每个变更都会发送到所有节点
watch机制
4.用于检测,服务发现
session:
每个ZooKeeper客户端的配置中都包括集合体中服务器的列表。在启动时,客户端会尝试连接到列表中的一台服务器。如果连接失败,它会尝试连接另一台服务器,以此类推,直到成功与一台服务器建立连接或因为所有ZooKeeper服务器都不可用而失败。
只要一个会话空闲超过一定时间,都可以通过客户端发送ping请求(也称为心跳)保持会话不过期。ping请求由ZooKeeper的客户端库自动发送,因此在我们的代码中不需要考虑如何维护会话。这个时间长度的设置应当足够低,以便能档检测出服务器故障(由读超时体现),并且能够在会话超时的时间段内重新莲接到另外一台服务器。

zookeeper数据同步过程

采用了递增的事务id号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上了zxid。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch,标识当前属于那个leader的统治时期。低32位用于递增计数。

  • zab protocol

    Leader election
        leader选举过程,electionEpoch自增,在选举的时候lastProcessedZxid越大,越有可能成为leader
    Discovery:
        第一:leader收集follower的lastProcessedZxid,这个主要用来通过和leader的lastProcessedZxid对比来确认follower需要同步的数据范围
        第二:选举出一个新的peerEpoch,主要用于防止旧的leader来进行提交操作(旧leader向follower发送命令的时候,follower发现zxid所在的peerEpoch比现在的小,则直接拒绝,防止出现不一致性)
    Synchronization:
        follower中的事务日志和leader保持一致的过程,就是依据follower和leader之间的lastProcessedZxid进行,follower多的话则删除掉多余部分,follower少的话则补充,一旦对应不上则follower删除掉对不上的zxid及其之后的部分然后再从leader同步该部分之后的数据
    Broadcast
        正常处理客户端请求的过程。leader针对客户端的事务请求,然后提出一个议案,发给所有的follower,一旦过半的follower回复OK的话,leader就可以将该议案进行提交了,向所有follower发送提交该议案的请求,leader同时返回OK响应给客户端

实际上zookeeper中算法三阶段:FSE=>Recovery=>Broadcast(广播和上面的一致)

  • fast leader election
    基于fast paxos。发送给所有的节点。没有随机leader参与收集。
    clipboard.png

    LOOKING:进入leader选举状态
    FOLLOWING:leader选举结束,进入follower状态
    LEADING:leader选举结束,进入leader状态
    OBSERVING:处于观察者状态
    1.serverA首先将electionEpoch自增,然后为自己投票
    2 serverB接收到上述通知,然后进行投票PK
    如果serverB收到的通知中的electionEpoch比自己的大,则serverB更新自己的electionEpoch为serverA的electionEpoch
    如果该serverB收到的通知中的electionEpoch比自己的小,则serverB向serverA发送一个通知,将serverB自己的投票以及electionEpoch发送给serverA,serverA收到后就会更新自己的electionEpoch
    在electionEpoch达成一致后,就开始进行投票之间的pk,优先比较proposedEpoch,然后优先比较proposedZxid,最后优先比较proposedLeader
    pk完毕后,如果本机器投票被pk掉,则更新投票信息为对方投票信息,同时重新发送该投票信息给所有的server。如果本机器投票没有被pk掉,如果是looking,过半更改状态,如果FOLLOWING/LEADING说明落后,加速收敛
  • Recovery
    略:https://my.oschina.net/pingpa...
  • follower读写过程图:

clipboard.png

ectd

经常应用于配置共享和服务发现,相比于zk,简单。使用 Go 语言编写部署简单;使用 HTTP 作为接口使用简单;使用 `Raft 算法`保证强一致性让用户易于理解。无需安装客户端。提供接口K-V存储(storing up to a few GB of data with consistent ordering,提供线性读),watch,lease,lock,election。因为共识完全实现的raft所以只简单说下部署模式,节点组成,数据持久化等。
官方:https://coreos.com/etcd/docs/latest/

架构图:

单节点如下
clipboard.png
store:为用户提供API
集群会区分proxy,leader,follower

  • 启动
    etcd 有三种集群化启动的配置方案,分别为静态配置启动、etcd 自身服务发现、通过 DNS 进行服务发现
    自身服务发现:
    首先就用自身单个的 url 构成一个集群,然后在启动的过程中根据参数进入discovery/discovery.go源码的JoinCluster函数。因为我们事先是知道启动时使用的 etcd 的 token 地址的,里面包含了集群大小 (size) 信息。在这个过程其实是个不断监测与等待的过程。启动的第一步就是在这个 etcd 的 token 目录下注册自身的信息,然后再监测 token 目录下所有节点的数量,如果数量没有达标,则循环等待。当数量达到要求时,才结束,进入正常的启动过程。
  • 运行
    proxy、leader\follower。proxy只负责转发,etcd proxy支持2种运行模式:readwrite和readonly,缺省的是readwrite,即proxy会将所有的读写请求都转发给etcd集群;readonly模式下,只转发读请求,写请求将会返回http 501错误。proxy保证参与投票数量有限的性能,所有follower都同步完数据才返回成功(因为异常不自动回来,可以全部,已经被管理员补齐了,否则只能读主,因此节点不能很多),在正常节点故障后,可以由管理员手动处理,一个备份的功能。
    etcd 可以代理访问 leader 节点的请求,所以如果你可以访问任何一个 etcd 节点,那么你就可以无视网络的拓扑结构对整个集群进行读写操作,否则只能连接leader.
    无故障自恢复(容易出错,考虑到已经高可用,管理员有时间自行恢复)
  • 数据持久:WAL+snapshot(删除WAL)
    从 snapshot 中获得集群的配置信息,包括 token、其他节点的信息等等,然后载入 WAL 目录的内容,从小到大进行排序。根据 snapshot 中得到的 term 和 index,找到 WAL 紧接着 snapshot 下一条的记录,然后向后更新,直到所有 WAL 包的 entry 都已经遍历完毕,Entry 记录到 ents 变量中存储在内存里。此时 WAL 就进入 append(read和append互斥) 模式,为数据项添加进行准备。
    当 WAL 文件中数据项内容过大达到设定值(默认为 10000)时,会进行 WAL 的切分,同时进行 snapshot 操作。这个过程可以在etcdserver/server.go的snapshot函数中看到。所以,实际上数据目录中有用的 snapshot 和 WAL 文件各只有一个,默认情况下 etcd 会各保留 5 个历史文件
  • 数据KV
    内存BTREE索引,物理B+树,存历史版本,没有引用后压缩删除历史版本。
  • 应用:https://www.infoq.cn/article/...

chubby

GFS和Big Table等大型系统都用他来解决分布式协作、元数据存储和Master选择等一系列与分布式锁服务相关的问题
客户端发送到其他机器都会将master反馈,重新转到master,持续直到换。
数据组织方式和zk一样。
只有主节点提供读写(数据和日志有空洞),高可靠和可用,吞吐量不如zk。在换主阶段会阻塞。

etcd,chubby,zk的对比

因为zk,etcd都会补齐follower。因此主从都可以读。etcd的主是固定的,除非故障=》raft的换主。chubby(06年)用过的mutil-poxas主一般不变,不保证每轮主从数据一致,只有主有读写能力,吞吐量会差一些,一万台机器分布式锁还是可以的。
etcd(14年)是后来的,肯定更好啊,有http接口,一切都更轻量简单,缺点只是无故障自恢复吧,zk每次都会选主(但基于一个xid,基本也类似mutil-poxas会稳定),可自动恢复。

你可能感兴趣的

载入中...