朴素Paxos(Basic Paxos)算法java简易实现
2019獨角獸企業重金招聘Python工程師標準>>>
Paxos算法是大名鼎鼎的Zookeeper中采用的選舉Leader的算法,事實上,在涉及到分布式系統的一致性的時候,就只有一種算法,那就是Paxos.
首先來看,Paxos是為了解決什么問題:
Paxos 算法解決的問題是一個分布式系統如何就某個值(決議)達成一致。一個典型的場景是,在一個分布式數據庫存儲中,如果各節點的初始狀態一致,每個節點執行相同的操作序列,那么他們最后能得到一個一致的狀態。為保證每個節點執行相同的命令序列,需要在每一條指令上執行一個“一致性算法”以保證每個節點看到的指令一致。一個通用的一致性算法可以應用在許多場景中,是分布式計算中的重要問題。因此從20世紀80年代起對于一致性算法的研究就沒有停止過。節點通信存在兩種模型:共享內存(Shared memory)和消息傳遞(Messages passing)。Paxos 算法就是一種基于消息傳遞模型的一致性算法。
本文的實現參考了LynnCui在知乎上的文章:https://zhuanlan.zhihu.com/p/21438357
有關Paxos的詳細介紹及推理過程均可參考上述文章,如果還有不明白,可以查閱相關文章,這一類的文章網上還是比較多的。
關于我的實現,我這里有幾點需要說明:
1.關于maxVote(即比本次表決編號小的最大表決編號),如果該編號存在,則應該用改編號對應表決的提案作為下一次的提案,否則,則可以隨機指定提案,具體的提案的指定方式依賴于需求邏輯,本程序中采用隨機提案;
2.Paxos假設消息的傳遞過程是不可靠的,這主要是因為實際環境中,網絡通信是沒有辦法保障的。網絡延遲以及服務器本身也有可能宕機,這些條件其實都在Paxos的前提假設之中,本程序當中做了一個假設,假設網絡通信有50%的概率失敗,實際上是我隨便指定的;
3.Java本身是一門繁瑣的語言,所以難免會有很大冗余代碼,這主要是為了保證程序的穩定性,還有一部分是出于習慣。Paxos其實并不是一個復雜的算法,至少基本的不是。程序中所采用的的面向對象的設計也不一定是合理的;
4.本程序依賴了Google的guava包以及common-lang3的包,實際中完全可以去掉。guava主要是在重載hashCode()方法的時候用到,但程序中并沒有使用hashCode的地方,實際只是因為在重載equals()方法的時候總是應該重載hashCode()方法。lang3主要做了字符串比較,應對字符串為空的情況。除此之外沒有任何依賴。
Java實現參考:
public final class PaxosDemo {private static final HashFunction HASH_FUNCTION = Hashing.murmur3_32();private static final Random RANDOM = new Random();private static final String[] PROPOSALS = {"ProjectA", "ProjectB", "ProjectC"};public static void main(String[] args) {List<Acceptor> acceptors = new ArrayList<Acceptor>();Arrays.asList("A", "B", "C", "D", "E").forEach(name -> acceptors.add(new Acceptor(name)));Proposer.vote(new Proposal(1L, null), acceptors);}private static void printInfo(String subject, String operation, String result) {System.out.println(subject + ":" + operation + "<" + result + ">");}/*** 對于提案的約束,第三條約束要求:* 如果maxVote不存在,那么沒有限制,下一次表決可以使用任意提案;* 否則,下一次表決要沿用maxVote的提案** @param currentVoteNumber* @param proposals* @return*/private static Proposal nextProposal(long currentVoteNumber, List<Proposal> proposals) {long voteNumber = currentVoteNumber + 1;if (proposals.isEmpty())return new Proposal(voteNumber, PROPOSALS[RANDOM.nextInt(PROPOSALS.length)]);Collections.sort(proposals);Proposal maxVote = proposals.get(proposals.size() - 1);long maxVoteNumber = maxVote.getVoteNumber();String content = maxVote.getContent();if (maxVoteNumber >= currentVoteNumber)throw new IllegalStateException("illegal state maxVoteNumber");if (content != null)return new Proposal(voteNumber, content);else return new Proposal(voteNumber, PROPOSALS[RANDOM.nextInt(PROPOSALS.length)]);}private static class Proposer {/*** @param proposal* @param acceptors*/public static void vote(Proposal proposal, Collection<Acceptor> acceptors) {int quorum = Math.floorDiv(acceptors.size(), 2) + 1;int count = 0;while (true) {printInfo("VOTE_ROUND", "START", ++count + "");List<Proposal> proposals = new ArrayList<Proposal>();for (Acceptor acceptor : acceptors) {Promise promise = acceptor.onPrepare(proposal);if (promise != null && promise.isAck())proposals.add(promise.getProposal());}if (proposals.size() < quorum) {printInfo("PROPOSER[" + proposal + "]", "VOTE", "NOT PREPARED");proposal = nextProposal(proposal.getVoteNumber(), proposals);continue;}int acceptCount = 0;for (Acceptor acceptor : acceptors) {if (acceptor.onAccept(proposal))acceptCount++;}if (acceptCount < quorum) {printInfo("PROPOSER[" + proposal + "]", "VOTE", "NOT ACCEPTED");proposal = nextProposal(proposal.getVoteNumber(), proposals);continue;}break;}printInfo("PROPOSER[" + proposal + "]", "VOTE", "SUCCESS");}}private static class Acceptor {//上次表決結果private Proposal last = new Proposal();private String name;public Acceptor(String name) {this.name = name;}public Promise onPrepare(Proposal proposal) {//假設這個過程有50%的幾率失敗if (Math.random() - 0.5 > 0) {printInfo("ACCEPTER_" + name, "PREPARE", "NO RESPONSE");return null;}if (proposal == null)throw new IllegalArgumentException("null proposal");if (proposal.getVoteNumber() > last.getVoteNumber()) {Promise response = new Promise(true, last);last = proposal;printInfo("ACCEPTER_" + name, "PREPARE", "OK");return response;} else {printInfo("ACCEPTER_" + name, "PREPARE", "REJECTED");return new Promise(false, null);}}public boolean onAccept(Proposal proposal) {//假設這個過程有50%的幾率失敗if (Math.random() - 0.5 > 0) {printInfo("ACCEPTER_" + name, "ACCEPT", "NO RESPONSE");return false;}printInfo("ACCEPTER_" + name, "ACCEPT", "OK");return last.equals(proposal);}}private static class Promise {private final boolean ack;private final Proposal proposal;public Promise(boolean ack, Proposal proposal) {this.ack = ack;this.proposal = proposal;}public boolean isAck() {return ack;}public Proposal getProposal() {return proposal;}}private static class Proposal implements Comparable<Proposal> {private final long voteNumber;private final String content;public Proposal(long voteNumber, String content) {this.voteNumber = voteNumber;this.content = content;}public Proposal() {this(0, null);}public long getVoteNumber() {return voteNumber;}public String getContent() {return content;}@Overridepublic int compareTo(Proposal o) {return Long.compare(voteNumber, o.voteNumber);}@Overridepublic boolean equals(Object obj) {if (obj == null)return false;if (!(obj instanceof Proposal))return false;Proposal proposal = (Proposal) obj;return voteNumber == proposal.voteNumber && StringUtils.equals(content, proposal.content);}@Overridepublic int hashCode() {return HASH_FUNCTION.newHasher().putLong(voteNumber).putString(content, Charsets.UTF_8).hash().asInt();}@Overridepublic String toString() {return new StringBuilder().append(voteNumber).append(':').append(content).toString();}}}以下是我試著運行了一下程序的結果:
VOTE_ROUND:START<1> ACCEPTER_A:PREPARE<OK> ACCEPTER_B:PREPARE<NO RESPONSE> ACCEPTER_C:PREPARE<OK> ACCEPTER_D:PREPARE<NO RESPONSE> ACCEPTER_E:PREPARE<NO RESPONSE> PROPOSER[1:null]:VOTE<NOT PREPARED> VOTE_ROUND:START<2> ACCEPTER_A:PREPARE<OK> ACCEPTER_B:PREPARE<NO RESPONSE> ACCEPTER_C:PREPARE<OK> ACCEPTER_D:PREPARE<NO RESPONSE> ACCEPTER_E:PREPARE<OK> ACCEPTER_A:ACCEPT<OK> ACCEPTER_B:ACCEPT<NO RESPONSE> ACCEPTER_C:ACCEPT<NO RESPONSE> ACCEPTER_D:ACCEPT<OK> ACCEPTER_E:ACCEPT<OK> PROPOSER[2:ProjectC]:VOTE<NOT ACCEPTED> VOTE_ROUND:START<3> ACCEPTER_A:PREPARE<OK> ACCEPTER_B:PREPARE<OK> ACCEPTER_C:PREPARE<OK> ACCEPTER_D:PREPARE<OK> ACCEPTER_E:PREPARE<OK> ACCEPTER_A:ACCEPT<OK> ACCEPTER_B:ACCEPT<NO RESPONSE> ACCEPTER_C:ACCEPT<NO RESPONSE> ACCEPTER_D:ACCEPT<NO RESPONSE> ACCEPTER_E:ACCEPT<NO RESPONSE> PROPOSER[3:ProjectB]:VOTE<NOT ACCEPTED> VOTE_ROUND:START<4> ACCEPTER_A:PREPARE<NO RESPONSE> ACCEPTER_B:PREPARE<NO RESPONSE> ACCEPTER_C:PREPARE<OK> ACCEPTER_D:PREPARE<OK> ACCEPTER_E:PREPARE<OK> ACCEPTER_A:ACCEPT<OK> ACCEPTER_B:ACCEPT<OK> ACCEPTER_C:ACCEPT<NO RESPONSE> ACCEPTER_D:ACCEPT<OK> ACCEPTER_E:ACCEPT<OK> PROPOSER[4:ProjectC]:VOTE<NOT ACCEPTED> VOTE_ROUND:START<5> ACCEPTER_A:PREPARE<OK> ACCEPTER_B:PREPARE<OK> ACCEPTER_C:PREPARE<NO RESPONSE> ACCEPTER_D:PREPARE<NO RESPONSE> ACCEPTER_E:PREPARE<OK> ACCEPTER_A:ACCEPT<NO RESPONSE> ACCEPTER_B:ACCEPT<OK> ACCEPTER_C:ACCEPT<NO RESPONSE> ACCEPTER_D:ACCEPT<NO RESPONSE> ACCEPTER_E:ACCEPT<NO RESPONSE> PROPOSER[5:ProjectB]:VOTE<NOT ACCEPTED> VOTE_ROUND:START<6> ACCEPTER_A:PREPARE<NO RESPONSE> ACCEPTER_B:PREPARE<OK> ACCEPTER_C:PREPARE<OK> ACCEPTER_D:PREPARE<OK> ACCEPTER_E:PREPARE<NO RESPONSE> ACCEPTER_A:ACCEPT<NO RESPONSE> ACCEPTER_B:ACCEPT<NO RESPONSE> ACCEPTER_C:ACCEPT<NO RESPONSE> ACCEPTER_D:ACCEPT<NO RESPONSE> ACCEPTER_E:ACCEPT<NO RESPONSE> PROPOSER[6:ProjectC]:VOTE<NOT ACCEPTED> VOTE_ROUND:START<7> ACCEPTER_A:PREPARE<OK> ACCEPTER_B:PREPARE<OK> ACCEPTER_C:PREPARE<OK> ACCEPTER_D:PREPARE<OK> ACCEPTER_E:PREPARE<NO RESPONSE> ACCEPTER_A:ACCEPT<OK> ACCEPTER_B:ACCEPT<OK> ACCEPTER_C:ACCEPT<OK> ACCEPTER_D:ACCEPT<NO RESPONSE> ACCEPTER_E:ACCEPT<NO RESPONSE> PROPOSER[7:ProjectB]:VOTE<SUCCESS>經過7輪表決終于達成一致.
轉載于:https://my.oschina.net/u/2541538/blog/807185
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的朴素Paxos(Basic Paxos)算法java简易实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ESXI6.5 最新版尝鲜安装图解
- 下一篇: Maven---学习心得---maven