共识算法 Paxos

Paxos 算法

Paxos 问题是指分布式的系统中存在故障(crash fault),但不存在恶意(corrupt)节点的场景(即可能消息丢失或重复,但无错误消息)下的共识达成问题。这也是分布式共识领域最为常见的问题。因为最早是 Leslie Lamport 用 Paxon 岛的故事模型来进行描述,而得以命名。解决 Paxos 问题的算法主要有 Paxos 系列算法和 Raft 算法。
作为后来很多共识算法(如 Raft、ZAB 等)的基础,Paxos 算法基本思想并不复杂。

基本原理

算法中存在三种逻辑角色的节点,在实现中同一节点可以担任多个角色。

  • 提案者(Proposer):提出一个提案,等待大家批准(Chosen)为结案(Value)。系统中提案都拥有一个自增的唯一提案号。往往由客户端担任该角色。
  • 接受者(Acceptor):负责对提案进行投票,接受(Accept)提案。往往由服务端担任该角色。
  • 学习者(Learner):获取批准结果,并帮忙传播,不参与投票过程。可为客户端或服务端。

算法需要满足安全性(Safety) 和存活性(Liveness)两方面的约束要求。实际上这两个基础属性也是大部分分布式算法都该考虑的。

  • Safety:保证决议(Value)结果是对的,无歧义的,不会出现错误情况。
    只有是被提案者提出的提案才可能被最终批准;
    在一次执行中,只批准(chosen)一个最终决议。被多数接受(accept)的结果成为决议;
  • Liveness:保证决议过程能在有限时间内完成。
    决议总会产生,并且学习者能获得被批准的决议。

基本思路类似两阶段提交:多个提案者先要争取到提案的权利(得到大多数接受者的支持);成功的提案者发送提案给所有人进行确认,得到大部分人确认的提案成为批准的结案。

实现 (Paxos 算法)

  Paxos 算法就是通过两个阶段确定一个决议:

  • Phase1:确定谁的编号最高,只有编号最高者才有权利提交 Proposal(提议:给定的具体值);
  • Phase2:编号最高者提交 Proposal,如果没有其他节点提出更高编号的 Proposal,则该提案会被顺利通过,否则整个过程就会重来。

  结论就是这个结论,至于整个过程的推导,就不在这里展开细说了。但是有一点需要注意的是,在过程第一阶段,可能会出现活锁。你编号高,我比你更高,反复如此,算法永远无法结束。可使用一个“Leader”来解决问题,这个 Leader 并非我们刻意去选出来一个,而是自然形成出来的。同样再次也不展开讨论了,本篇主要是以 Code 为主的哈!

Phase1

func (px *Paxos)Prepare(args *PrepareArgs, reply *PrepareReply) error {
	px.mu.Lock()
	defer px.mu.Unlock()
	round, exist := px.rounds[args.Seq]
	if !exist {
		//new seq  of commit,so need new
		px.rounds[args.Seq] = px.newInstance()
		round, _ = px.rounds[args.Seq]
		reply.Err = OK
	}else {
		if args.PNum > round.proposeNumber {
			reply.Err = OK
		}else {
			reply.Err = Reject
		}
	}
	if reply.Err == OK {
		reply.AcceptPnum = round.acceptorNumber
		reply.AcceptValue = round.acceptValue
		px.rounds[args.Seq].proposeNumber = args.PNum
	}else {
		//reject
	}
	return nil
}

  在 Prepare 阶段,主要是通过 RPC 调用,询问每一台机器,当前的这个提议能不能通过,判断的条件就是,当前提交的编号大于之前的其他机器 Prepare 的编号,代码 if args.PNum > round.proposeNumber 的判断。还有一个就是,如果之前一台机器都没有通过,即使当前是第一个提交 Prepare 的机器,那就直接同意通过了。代码片段:

round, exist := px.rounds[args.Seq]
	if !exist {
		//new seq  of commit,so need new
		px.rounds[args.Seq] = px.newInstance()
		round, _ = px.rounds[args.Seq]
		reply.Err = OK
	}

  在完成逻辑判断过后,如果本次提议是通过的,那么还需返回给提议者,已经通过提议和确定的值。代码片段:

if reply.Err == OK {
	reply.AcceptPnum = round.acceptorNumber
	reply.AcceptValue = round.acceptValue
	px.rounds[args.Seq].proposeNumber = args.PNum
}

Phase2

func (px Paxos)Accept(args *AcceptArgs, reply *AcceptReply) error {
	px.mu.Lock()
	defer px.mu.Unlock()
	round, exist := px.rounds[args.Seq]
	if !exist {
		px.rounds[args.Seq] = px.newInstance()
		reply.Err = OK
	}else {
		if args.PNum >= round.proposeNumber {
			reply.Err = OK
		}else {
			reply.Err = Reject
		}
	}
	if reply.Err == OK {
		px.rounds[args.Seq].acceptorNumber = args.PNum
		px.rounds[args.Seq].proposeNumber = args.PNum
		px.rounds[args.Seq].acceptValue = args.Value
	}else {
		//reject
	}
	return nil
}

  在 Accept 阶段基本和 Prepare 阶段如出一辙咯。判断当前的提议是否存在,如果不纯在表明是新的,那就直接返回 OK 咯!

round, exist := px.rounds[args.Seq]
if !exist {
	px.rounds[args.Seq] = px.newInstance()
	reply.Err = OK
}

  然后同样判断提议号是否大于等于当前的提议编号,如果是,那同样也返回 OK 咯,否者就拒绝。

if args.PNum >= round.proposeNumber {
	reply.Err = OK
}else {
	reply.Err = Reject
}

  与此重要的一点就是,如果提议通过,那么就需设置当轮的提议编号和提议的值。

if reply.Err == OK {
	px.rounds[args.Seq].acceptorNumber = args.PNum
	px.rounds[args.Seq].proposeNumber = args.PNum
	px.rounds[args.Seq].acceptValue = args.Value
}

  整个使用过程中使用了 Map 和数组来存储一些辅助信息,Map 主要存储的是,每一轮的投票被确定的结果,Key 表示每一轮的投票编号,Round 表示存储已经接受的值。Completes 数组主要是用于存储在使用的过程中,已经确定完成了的最小的编号。

	rounds     map[int]*Round //cache each round  paxos result key is seq value is value
	completes  [] int         //maintain peer min seq of completed

func (px *Paxos)Decide(args *DecideArgs, reply *DecideReply) error {
	px.mu.Lock()
	defer px.mu.Unlock()
	_, exist := px.rounds[args.Seq]
	if !exist {
		px.rounds[args.Seq] = px.newInstance()
	}
	px.rounds[args.Seq].acceptorNumber = args.PNum
	px.rounds[args.Seq].acceptValue = args.Value
	px.rounds[args.Seq].proposeNumber = args.PNum
	px.rounds[args.Seq].state = Decided
	px.completes[args.Me] = args.Done
	return nil
}

  同时 Decide 方法,用于提议者来确定某个值,这个映射到分布式里面的状态机的应用。

在这里插入图片描述

  客户段通过提交指令给服务器,服务器通过 Paxos 算法是现在多台机器上面,所有的服务器按照顺序执行相同的指令,然后状态机对指令进行执行最后每台机器的结果都是一样的。

在这里插入图片描述

故障恢复

  在分布式环境之中,网络故障宕机属于正常现象。如果一台机器宕机了,过了一段时间又恢复了,那么他宕机的时间之中,怎么将之前的指令恢复回来?当他提交一个 jmp 指令的时候,索引 1、2 都是已经确定了的指令,所以可以直接从索引 3 开始,当他提交 Propser(jmp)的时候,会收到 s1、s3 的返回值(cmp),根据 Paxos 算法后者认同前者的原则,所以他会在 Phase2 阶段提交一个值为 cmp accept 的请求,最后索引为 3 的就变成了 cmp,如果说在这个阶段没有返回值,那么就选择客户端的返回值就可以了,最后就达成了一致。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值