Paxos分布式一致性算法及Java实现
阅读《从Paxos到Zookeeper》一书第二章,学习Paxos算法。
这个算法对我这个小菜鸡来说,看一遍书还是非常晦涩难懂的,所以不断参考网上生动形象的讲解及重复看了好多遍书上的这块内容,算是略懂一点了。
但是现在还有好几个疑惑点:
- 主Proposer如何选定,多个Proposer时,选定一个提案后其它操作全部停止吗?
- Prepare和Accept过程Acceptor返回的是响应过的还是批准过的最大编号?
- 最后提案是通过Proposer还是Acceptor给Learner?
算法主要内容
分为三个主要角色
Proposer:提案者
Acceptor:投票者
Learner:学习者
主要分为两个阶段
阶段一:
(a) Proposer选择一个提案编号N,然后向半数以上的Acceptor发送编号为N的Prepare请求。
(b) 如果一个Acceptor收到一个编号为N的Prepare请求,且N大于该Acceptor已经响应过的所有Prepare请求的编号,那么它就会将它已经接受过的编号最大的提案(如果有的话)作为响应反馈给Proposer,同时该Acceptor承诺不再接受任何编号小于N的提案。
阶段二:
(a) 如果Proposer收到半数以上Acceptor对其发出的编号为N的Prepare请求的响应,那么它就会发送一个针对[N,V]提案的Accept请求给半数以上的Acceptor。注意:V就是收到的响应中编号最大的提案的value,如果响应中不包含任何提案,那么V就由Proposer自己决定。
(b) 如果Acceptor收到一个针对编号为N的提案的Accept请求,只要该Acceptor没有对编号大于N的Prepare请求做出过响应,它就接受该提案。
代码实现
package mode;
public class Acceptor {
private String name;
private Integer maxKey;
private Integer maxValue;
private Integer acceptKey;
private Integer acceptValue;
public Integer getMaxKey() {
return maxKey;
}
public void setMaxKey(Integer maxKey) {
this.maxKey = maxKey;
}
public Integer getAcceptKey() {
return acceptKey;
}
public void setAcceptKey(Integer acceptKey) {
this.acceptKey = acceptKey;
}
public Integer getAcceptValue() {
return acceptValue;
}
public void setAcceptValue(Integer acceptValue) {
this.acceptValue = acceptValue;
}
public Integer getMaxValue() {
return maxValue;
}
@Override
public String toString() {
return "Acceptor [name=" + name + ", maxKey=" + maxKey + ", maxValue=" + maxValue + ", acceptKey=" + acceptKey
+ ", acceptValue=" + acceptValue + "]";
}
public Acceptor() {
super();
}
public Acceptor(String name) {
super();
this.name = name;
this.acceptKey = null;
this.acceptValue = null;
this.maxKey = null;
this.maxValue = null;
}
public void setMaxValue(Integer maxValue) {
this.maxValue = maxValue;
}
public static PerpareResp doPerpare(String p, Acceptor acceptor, Perpare perpare) {
//假定50%概率失败
if(Math.random()>0.5) {
System.out.println(p+" perpare: "+acceptor+perpare+" error");
return new PerpareResp(false, null, null);
}
if(acceptor.getMaxKey() == null) {
acceptor.setMaxKey(perpare.getKey());
acceptor.setMaxValue(perpare.getValue());
System.out.println(p+" perpare: "+acceptor+perpare+" ok");
return new PerpareResp(true, null, null);
}
if(acceptor.getMaxKey()<perpare.getKey()) {
Integer k = acceptor.getMaxKey();
Integer v = acceptor.getMaxValue();
acceptor.setMaxKey(perpare.getKey());
acceptor.setMaxValue(perpare.getValue());
System.out.println(p+" perpare: "+acceptor+perpare+" ok");
return new PerpareResp(true, k, v);
}
System.out.println(p+" perpare: "+acceptor+perpare+" error");
return new PerpareResp(false, null, null);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static AcceptResp doAccept(String p, Acceptor acceptor, Accept accept) {
//假定50%概率失败
if(Math.random()>0.5) {
System.out.println(p+" accect: "+acceptor+accept+" error");
return new AcceptResp(false, null, null);
}
if(acceptor.getMaxKey() == null) {
acceptor.setAcceptKey(accept.getKey());
acceptor.setAcceptValue(accept.getValue());
System.out.println(p+" accect: "+acceptor+accept+" ok");
return new AcceptResp(true, acceptor.getAcceptKey(), acceptor.getAcceptValue());
}
if(acceptor.getMaxKey() <= accept.getKey()) {
Integer k = acceptor.getAcceptKey();
Integer v = acceptor.getAcceptValue();
acceptor.setMaxKey(accept.getKey());
acceptor.setMaxValue(accept.getValue());
acceptor.setAcceptKey(accept.getKey());
acceptor.setAcceptValue(accept.getValue());
System.out.println(p+" accect: "+acceptor+accept+" ok");
return new AcceptResp(true, k, v);
}
System.out.println("accect: "+acceptor+accept+" error");
return new AcceptResp(false, null, null);
}
}
package mode;
public class Proposer {
@Override
public String toString() {
return "Proposer [name=" + name + "]";
}
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package mode;
public class Perpare {
@Override
public String toString() {
return "Perpare [key=" + key + ", value=" + value + "]";
}
private Integer key;
private Integer value;
public Perpare(Integer key, Integer value) {
super();
this.key = key;
this.value = value;
}
public Integer getKey() {
return key;
}
public void setKey(Integer key) {
this.key = key;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
public static Perpare nextPerpare(Integer oldKey, Integer oldValue) {
if(oldKey==null || oldValue == null) {
return new Perpare(Number.index++, (int)(Math.random()*100));
}else {
return new Perpare(Number.index++, oldValue);
}
}
}
package mode;
public class Accept {
private Integer key;
private Integer value;
public Integer getKey() {
return key;
}
public void setKey(Integer key) {
this.key = key;
}
public Integer getValue() {
return value;
}
@Override
public String toString() {
return "Accept [key=" + key + ", value=" + value + "]";
}
public Accept(Integer key, Integer value) {
super();
this.key = key;
this.value = value;
}
public void setValue(Integer value) {
this.value = value;
}
}
package mode;
public class PerpareResp {
@Override
public String toString() {
return "PerpareResp [status=" + status + ", key=" + key + ", value=" + value + "]";
}
public PerpareResp(boolean status, Integer key, Integer value) {
super();
this.status = status;
this.key = key;
this.value = value;
}
private boolean status;
private Integer key;
private Integer value;
public boolean isStatus() {
return status;
}
public void setStatus(boolean status) {
this.status = status;
}
public Integer getKey() {
return key;
}
public void setKey(Integer key) {
this.key = key;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
}
package mode;
public class AcceptResp {
public AcceptResp(boolean status, Integer key, Integer value) {
super();
this.status = status;
this.key = key;
this.value = value;
}
private boolean status;
private Integer key;
private Integer value;
public boolean isStatus() {
return status;
}
public void setStatus(boolean status) {
this.status = status;
}
public Integer getKey() {
return key;
}
public void setKey(Integer key) {
this.key = key;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
}
package mode;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class PaxosThread extends Thread {
private Thread thread;
private Proposer proposer;
private List<Acceptor> acceptors;
public PaxosThread(Proposer proposer, List<Acceptor> acceptors) {
this.proposer = proposer;
this.acceptors = acceptors;
}
@Override
public void run() {
int quorum = Math.floorDiv(acceptors.size(), 2) + 1;
Perpare perpare = Perpare.nextPerpare(null, null);
int count = 0;
while (true) {
System.out.println("proposer:" + proposer + " count:" + count + " perpare:" + perpare);
List<PerpareResp> perpareResps = new ArrayList<>();
for (int i = 0; i < acceptors.size(); i++) {
Acceptor acceptor = acceptors.get(i);
PerpareResp r = Acceptor.doPerpare(proposer.getName(),acceptor, perpare);
if(r.isStatus()) {
perpareResps.add(r);
}
}
System.out.println();
perpareResps = perpareResps.stream().sorted(Comparator.comparing(PerpareResp::getKey, Comparator.nullsFirst(Integer::compareTo))).collect(Collectors.toList());
if(perpareResps.size()<quorum) {
PerpareResp r = new PerpareResp(true, null, null);
if(perpareResps.size()!=0)
r = perpareResps.get(perpareResps.size()-1);
perpare = Perpare.nextPerpare(r.getKey(), r.getValue());
continue;
}
Accept accept = new Accept(perpare.getKey(), perpare.getValue());
List<AcceptResp> acceptResps = new ArrayList<>();
for (int i = 0; i < acceptors.size(); i++) {
Acceptor acceptor = acceptors.get(i);
AcceptResp r = Acceptor.doAccept(proposer.getName(),acceptor, accept);
if(r.isStatus()) {
acceptResps.add(r);
}
}
System.out.println();
if(acceptResps.size()<quorum) {
perpare = Perpare.nextPerpare(accept.getKey(), accept.getValue());
continue;
}
break;
}
System.out.println("select "+perpare);
}
public void start() {
if (thread == null) {
thread = new Thread(this,"start thread");
thread.start();
}
}
}
package mode;
import java.util.ArrayList;
import java.util.List;
public class Paxos{
public static void doVote() {
Proposer proposer = new Proposer();
proposer.setName("P1");
Proposer proposer1 = new Proposer();
proposer1.setName("P2");
Proposer proposer2 = new Proposer();
proposer2.setName("P3");
List<Acceptor> acceptors = new ArrayList<>();
for(int i = 0; i < 5; i++) {
String s = "A" + i;
Acceptor acceptor = new Acceptor(s);
acceptors.add(acceptor);
}
PaxosThread p = new PaxosThread(proposer, acceptors);
PaxosThread p1 = new PaxosThread(proposer1, acceptors);
PaxosThread p2 = new PaxosThread(proposer2, acceptors);
System.out.println("start");
p.start();
p1.start();
p2.start();
}
public static void main(String... arg) {
doVote();
}
}