├── .gitignore ├── .idea ├── misc.xml └── modules.xml ├── README.md ├── class-diagram-1.png ├── class-diagram-2.png ├── config └── src └── java ├── Paxos-log-replication-Java.iml └── main ├── paxos ├── Replica.java └── synod │ ├── Acceptor.java │ └── Leader.java └── utilities ├── BallotComparator.java ├── Environment.java ├── PaxosMsgs.java ├── SingleNodeEnvironment.java └── message_specification.proto /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Java template 3 | *.class 4 | 5 | # BlueJ files 6 | *.ctxt 7 | 8 | # Mobile Tools for Java (J2ME) 9 | .mtj.tmp/ 10 | 11 | # Package Files # 12 | *.jar 13 | *.war 14 | *.ear 15 | 16 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 17 | hs_err_pid* 18 | 19 | 20 | 21 | # Created by https://www.gitignore.io/api/macos,intellij 22 | 23 | ### macOS ### 24 | *.DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must end with two \r 29 | Icon 30 | # Thumbnails 31 | ._* 32 | # Files that might appear in the root of a volume 33 | .DocumentRevisions-V100 34 | .fseventsd 35 | .Spotlight-V100 36 | .TemporaryItems 37 | .Trashes 38 | .VolumeIcon.icns 39 | .com.apple.timemachine.donotpresent 40 | # Directories potentially created on remote AFP share 41 | .AppleDB 42 | .AppleDesktop 43 | Network Trash Folder 44 | Temporary Items 45 | .apdisk 46 | 47 | 48 | ### Intellij ### 49 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 50 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 51 | 52 | # User-specific stuff: 53 | .idea/workspace.xml 54 | .idea/tasks.xml 55 | 56 | # Sensitive or high-churn files: 57 | .idea/dataSources/ 58 | .idea/dataSources.ids 59 | .idea/dataSources.xml 60 | .idea/dataSources.local.xml 61 | .idea/sqlDataSources.xml 62 | .idea/dynamic.xml 63 | .idea/uiDesigner.xml 64 | 65 | # Gradle: 66 | .idea/gradle.xml 67 | .idea/libraries 68 | 69 | # Mongo Explorer plugin: 70 | .idea/mongoSettings.xml 71 | 72 | ## File-based project format: 73 | *.iws 74 | 75 | ## Plugin-specific files: 76 | 77 | # IntelliJ 78 | /out/ 79 | 80 | # mpeltonen/sbt-idea plugin 81 | .idea_modules/ 82 | 83 | # JIRA plugin 84 | atlassian-ide-plugin.xml 85 | 86 | # Crashlytics plugin (for Android Studio and IntelliJ) 87 | com_crashlytics_export_strings.xml 88 | crashlytics.properties 89 | crashlytics-build.properties 90 | fabric.properties 91 | 92 | ### Intellij Patch ### 93 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 94 | 95 | # *.iml 96 | # modules.xml 97 | # .idea/misc.xml 98 | # *.ipr 99 | 100 | # End of https://www.gitignore.io/api/macos,intellij -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Paxos Java implementation 2 | 3 | ## What is this? 4 | 5 | Implementation of multi-paxos protocol. 6 | 7 | ## How to demo this project? 8 | 9 | #### How to build? 10 | 11 | I use Intellij to manage my project. To build project, just 12 | import project to Intellij, don't forget to add google-protobuf library, then build. 13 | 14 | #### How to run? 15 | 16 | * First, in "config" file, decide the system topology(how many leaders, replicas and acceptors in the system). 17 | 18 | * The main method is in utilities.SingleNodeEnvironment. It accepts two args: the first is config file path, and the second is the number of requests(RSM commands) that need to be decided by Paxos protocol. 19 | 20 | * Run the program, you will see the result in console. 21 | 22 | ## Understand Paxos protocol 23 | 24 | ####Just read these papers 25 | 26 | * The Part-Time Parliament, by Leslie Lamport 27 | * Paxos Made Simple, by Leslie Lamport 28 | * Paxos Made Live, by Tushar Chandra, Robert Griesemer and Joshua Redstone 29 | * Paxos Made Moderately Complex, by ROBBERT VAN RENESSE and DENIZ ALTINBUKEN 30 | 31 | #### Basic distributed computing concepts related to Paxos 32 | 33 | * Time models -- synchronous, asynchronous, partial synchronous 34 | * Failure models -- fail-stop, byzantine 35 | * Definition of the consensus problem -- agreement, validity, termination 36 | * Replia state machine(RSM) 37 | * The FLP impossible result 38 | * And other RSM protocols -- Zab(Zookeeper), Viewstamp, Raft (Paxos, Viewstamp and Raft are based on similar ideas) 39 | 40 | An excellent must-read book -- Distributed Algorithms, by Nancy A. Lynch 41 | 42 | ## Understand the code 43 | 44 | Here is the class diagram of key classed in the project: 45 | ![class diagram](https://github.com/BBQyuki/Paxos-log-replication-Java/blob/master/class-diagram-1.png) 46 | 47 | #### Some explanation 48 | 49 | * Replica, Leader and Acceptor are three key roles in Paxos protocol. 50 | 51 | * Do not confuse with 'Leader' here. The Leader in Paxos is the conductor of each ballot. You may think what is the difference between Leader here and Leader in Raft or Viewstamp. 52 | Leader in Raft or Viewstamp is more like the "prime" leader of all leaders in Paxos -- Raft and Viewstamp do a leader election. In Paxos, every leader can propose, while in Raft and Viewstamp, only the selected 'prime' leader can propose. 53 | 54 | * Scout and Commander are two helping roles in Leader. Scout is for phase 1 of protocol and Command for phase 2. They are designed as inner classes in Leader for simplicity. 55 | 56 | 57 | ##### For more infomation, pls see comments in code. 58 | -------------------------------------------------------------------------------- /class-diagram-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SabaPing/Paxos-log-replication-Java/3fb39cd86ff786a85b88871814aa576d317735a4/class-diagram-1.png -------------------------------------------------------------------------------- /class-diagram-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SabaPing/Paxos-log-replication-Java/3fb39cd86ff786a85b88871814aa576d317735a4/class-diagram-2.png -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | * Topology assumption: 2 | * every client has bidirectional channel to every replica, 3 | * every replica has bidirectional channel to every leader, 4 | * and every leader has bidirectional channel to every acceptor. 5 | 6 | * a pair of ip address and port# uniquely defines a role. 7 | * below define all roles, for system with f <= 1. A role is assigned to a host as: 8 | 9 | * Format:Role ID 10 | 11 | * replicas 12 | R 1 13 | R 2 14 | 15 | * leaders 16 | L 5 17 | L 6 18 | 19 | * acceptors 20 | A 9 21 | A 10 22 | A 11 23 | 24 | -------------------------------------------------------------------------------- /src/java/Paxos-log-replication-Java.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/java/main/paxos/Replica.java: -------------------------------------------------------------------------------- 1 | package paxos; 2 | 3 | import utilities.Environment; 4 | import utilities.PaxosMsgs.*; 5 | 6 | import static utilities.PaxosMsgs.Paxos.Type.*; 7 | 8 | import java.util.*; 9 | 10 | /** 11 | * Invariants for replicas: 12 | * R1: There are no two different commands decided for the same slot. 13 | * R2: All commands up to slot out are in the set of decisions. 14 | * R3: For all replicas ρ, ρ.state is the result of applying 15 | * the commands⟨s,cs⟩∈ρ.decisions to initial_state for all s up to slot_out, 16 | * in order of slot number. 17 | * R4: For each ρ, the variable ρ.slot out cannot decrease over time. 18 | * R5: A replica proposes commands only for slots for which it knows the configuration. 19 | *

20 | * And R1 is ensured by synod(i.e., simple paxos) 21 | *

22 | * Did not implement reconfig function!!! 23 | */ 24 | public class Replica extends Thread { 25 | //paxos related thread 26 | private int slot_in; 27 | private int slot_out; 28 | private final Queue requests; 29 | private final Map proposals; 30 | private final Map decisions; 31 | private final int ID; 32 | 33 | //for configuration change 34 | private final int WINDOW; 35 | 36 | //For sending and receiving messages 37 | private final Environment environment; 38 | 39 | public Replica(int ID, Environment environment) { 40 | super("Replica-" + ID); 41 | this.ID = ID; 42 | this.environment = environment; 43 | this.decisions = new HashMap<>(); 44 | this.proposals = new HashMap<>(); 45 | this.requests = new ArrayDeque<>(); 46 | this.slot_in = 1; 47 | this.slot_out = 1; 48 | 49 | //???ease conflicts between ballot??? 50 | this.WINDOW = 3; 51 | } 52 | 53 | /** 54 | * Transfer requests from requests to proposals 55 | */ 56 | private void propose() { 57 | while ((slot_in < slot_out + WINDOW) && !requests.isEmpty()) { 58 | //if not already proposed for slot_in 59 | if (!decisions.containsKey(slot_in)) { 60 | Command polledCmd = requests.poll(); 61 | proposals.put(slot_in, polledCmd); 62 | 63 | Paxos temp_propose = Paxos.newBuilder() 64 | .setType(PROPOSE) 65 | .setPropose(Propose.newBuilder() 66 | .setSlotIn(slot_in) 67 | .setC(polledCmd)) 68 | .build(); 69 | 70 | for (int leader : environment.getLeaders()) { 71 | environment.send(leader, temp_propose); 72 | } 73 | } 74 | slot_in++; 75 | } 76 | } 77 | 78 | /** 79 | * The function perform() is invoked with the same sequence of commands at all replicas. 80 | * Different replicas may end up proposing the same command for different slots, 81 | * and thus the same command may be decided multiple times. 82 | */ 83 | private void perform(Command cmd) { 84 | //if it has already performed the command 85 | for (int i = 1; i < slot_out; i++) { 86 | if (decisions.get(i).equals(cmd)) { 87 | slot_out++; 88 | return; 89 | } 90 | } 91 | 92 | System.out.println("Replica " + ID + ": perform slot " + slot_out + " command " + cmd.getOperation()); 93 | slot_out++; 94 | 95 | /** 96 | * for debugging and simplicity, no client here 97 | */ 98 | // Paxos temp_response = Paxos.newBuilder() 99 | // .setType(RESPONSE) 100 | // .setResponse(Response.newBuilder() 101 | // .setCid(cmd.getCid()) 102 | // .setResult("Performed")) 103 | // .build(); 104 | // 105 | // environment.send(cmd.getClient(), temp_response); 106 | } 107 | 108 | public void run() { 109 | while (true) { 110 | Paxos incMsg = environment.receive(ID); 111 | switch (incMsg.getType()) { 112 | case REQUEST: { 113 | requests.offer(incMsg.getRequest().getC()); 114 | break; 115 | } 116 | 117 | /** 118 | * Decisions may arrive out of order and multiple times 119 | */ 120 | case DECISION: { 121 | //adds the decision to the set decisions 122 | Decision body = incMsg.getDecision(); 123 | decisions.put(body.getSlotNum(), body.getC()); 124 | 125 | /** 126 | * If there is a decision c′ corresponding to the current slot out, 127 | * the replica first checks to see if it has proposed a command c′′ for that slot. 128 | * If so, the replica removes ⟨slot out,c′′⟩ from the set proposals. 129 | * If c′′ ̸= c′, the replica returns c′′ to set requests 130 | */ 131 | while (decisions.containsKey(slot_out)) { 132 | if (proposals.containsKey(slot_out)) { 133 | if (!proposals.get(slot_out).equals(decisions.get(slot_out))) { 134 | requests.offer(proposals.get(slot_out)); 135 | } 136 | proposals.remove(slot_out); 137 | } 138 | perform(decisions.get(slot_out)); 139 | } 140 | } 141 | } 142 | propose(); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/java/main/paxos/synod/Acceptor.java: -------------------------------------------------------------------------------- 1 | package paxos.synod; 2 | 3 | import utilities.BallotComparator; 4 | import utilities.Environment; 5 | 6 | import java.util.*; 7 | 8 | import utilities.PaxosMsgs.*; 9 | 10 | import static utilities.PaxosMsgs.Paxos.Type.*; 11 | 12 | /** 13 | * If process is an acceptor, it only has a single thread. 14 | */ 15 | public class Acceptor extends Thread { 16 | 17 | //Paxos related states 18 | private Ballot acceptorBallot; 19 | private final Set accepted; 20 | private final int ID; 21 | 22 | //For sending and receiving messages 23 | private final Environment environment; 24 | 25 | //Initial states 26 | public Acceptor(int id, Environment environment) { 27 | super("Acceptor-" + id); 28 | acceptorBallot = Ballot.newBuilder() 29 | .setPrefix(0) 30 | .setConductor(0) 31 | .build(); 32 | accepted = new HashSet<>(); 33 | this.environment = environment; 34 | this.ID = id; 35 | } 36 | 37 | @Override 38 | public void run() { 39 | while (true) { 40 | //For each message received, do something 41 | Paxos message = environment.receive(ID); 42 | 43 | //Acceptor can receive two types of messages 44 | switch (message.getType()) { 45 | //received a p1a message from a scout 46 | case P1A: { 47 | P1a body = message.getP1A(); 48 | 49 | //if received ballot is larger than acceptor's ballot 50 | if (new BallotComparator().compare(body.getBallot(), acceptorBallot) > 0) 51 | acceptorBallot = body.getBallot(); 52 | 53 | // reply to the scout 54 | environment.send(body.getFromLeader(), 55 | Paxos.newBuilder() 56 | .setType(P1B) 57 | .setP1B(P1b.newBuilder() 58 | .setFrom(ID) 59 | .setToScout(body.getFromScout()) 60 | .setBallot(acceptorBallot) 61 | .setAccepted(Accepted.newBuilder() 62 | .addAllPvalue(accepted))) 63 | .build()); 64 | break; 65 | } 66 | //p2a from a commander, code almost the same 67 | case P2A: { 68 | P2a body = message.getP2A(); 69 | if (new BallotComparator().compare(body.getPvalue().getBallot(), acceptorBallot) == 0) 70 | accepted.add(body.getPvalue()); 71 | 72 | environment.send(body.getFromLeader(), 73 | Paxos.newBuilder() 74 | .setType(P2B) 75 | .setP2B(P2b.newBuilder() 76 | .setFrom(ID) 77 | .setToCommander(body.getFromCommander()) 78 | .setBallot(acceptorBallot)) 79 | .build()); 80 | break; 81 | } 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/java/main/paxos/synod/Leader.java: -------------------------------------------------------------------------------- 1 | package paxos.synod; 2 | 3 | import utilities.BallotComparator; 4 | import utilities.PaxosMsgs.*; 5 | import utilities.Environment; 6 | 7 | import java.util.*; 8 | import java.util.concurrent.ArrayBlockingQueue; 9 | import java.util.concurrent.BlockingQueue; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | import java.util.concurrent.atomic.AtomicInteger; 12 | import static utilities.PaxosMsgs.Paxos.Type.*; 13 | 14 | 15 | /** 16 | * The Leader class has two Paxos related inner classes, which are Scout and Commander. 17 | * The leader is the main thread. Scouts and Commanders are launched by the leader. 18 | * Use inner classes to simplify state sharing. 19 | * 20 | * Inter threads communication is done by message queues and MessageHandler thread. 21 | * For one-to-one mapping between each thread and its message queue, each thread need a local id, 22 | * Use ThreadLocal to implement thread local id. 23 | * 24 | * All threads are executed in an almost-asynchronous manner, 25 | * the only very weak synchronized part are message queues. 26 | */ 27 | public class Leader extends Thread{ 28 | 29 | //Paxos related states, no one is shared, thus thread safe. 30 | private Ballot leaderBallot; 31 | private final Map proposals; 32 | private final int ID; 33 | private boolean active; 34 | 35 | //shared states for inter-threads communication, need thread-safe mechanism 36 | private final Map> messageQueues; 37 | private final BlockingQueue incomingMessages; 38 | 39 | //sending and receiving messages 40 | private final Environment environment; 41 | 42 | public Leader(int id, Environment environment) { 43 | super("Leader-" + id); 44 | ID = id; 45 | leaderBallot = Ballot.newBuilder().setPrefix(0).setConductor(id).build(); 46 | active = false; 47 | proposals = new HashMap<>(); 48 | 49 | this.environment = environment; 50 | 51 | //initial and register message queue 52 | messageQueues = new ConcurrentHashMap<>(); 53 | this.incomingMessages = new ArrayBlockingQueue<>(100); 54 | } 55 | 56 | @Override 57 | public void run() { 58 | new MessageHandler(ThreadID.get()).start(); 59 | messageQueues.putIfAbsent(ThreadID.get(), this.incomingMessages); 60 | 61 | //To ensure states is not shared, build a new ballot 62 | new Scout(Ballot.newBuilder(leaderBallot).build()).start(); 63 | 64 | try { 65 | while (true) 66 | forever(); 67 | } catch (InterruptedException e) { 68 | e.printStackTrace(); 69 | } finally { 70 | //leader never end. not necessary to de-register its message Q. 71 | } 72 | } 73 | 74 | /** 75 | * this method transit shared states, need careful concurrent design 76 | * @throws InterruptedException 77 | */ 78 | private void forever() throws InterruptedException{ 79 | Paxos incMsg = incomingMessages.take(); 80 | switch (incMsg.getType()) { 81 | case PROPOSE: { 82 | Propose temp_propose = incMsg.getPropose(); 83 | if (!proposals.containsKey(temp_propose.getSlotIn())){ 84 | proposals.put(temp_propose.getSlotIn(), temp_propose.getC()); 85 | if (active) { 86 | /** 87 | * spawn a Commander 88 | * have some concerns! Is protobuf really immutable??? 89 | * Assume it is! 90 | */ 91 | new Commander(PValue.newBuilder() 92 | .setBallot(leaderBallot) 93 | .setSlotNum(temp_propose.getSlotIn()) 94 | .setCmd(temp_propose.getC()) 95 | .build()).start(); 96 | } 97 | } 98 | } 99 | case ADOPTED: { 100 | Adopted temp_adopted = incMsg.getAdopted(); 101 | if (leaderBallot.equals(temp_adopted.getBallot())){ 102 | /** 103 | * updates the set of proposals, replacing for each slot number 104 | * the command corresponding to the maximum pvalue in pvals, if any. 105 | */ 106 | Map pmax = new HashMap<>(); 107 | for(PValue pv : temp_adopted.getPvalues().getPvalueList()){ 108 | if(!pmax.containsKey(pv.getSlotNum()) || 109 | new BallotComparator().compare(pmax.get(pv.getSlotNum()), pv.getBallot()) < 0) 110 | proposals.put(pv.getSlotNum(), pv.getCmd()); 111 | } 112 | for(Map.Entry entry : proposals.entrySet()) { 113 | PValue temp_pv = PValue.newBuilder() 114 | .setBallot(leaderBallot) 115 | .setSlotNum(entry.getKey()) 116 | .setCmd(entry.getValue()) 117 | .build(); 118 | new Commander(temp_pv).start(); 119 | } 120 | active = true; 121 | } 122 | break; 123 | } 124 | case PREEMPTED: { 125 | Preempted temp_preempted = incMsg.getPreempted(); 126 | if (new BallotComparator().compare(temp_preempted.getBallot(), leaderBallot) > 0) { 127 | active = false; 128 | leaderBallot = Ballot.newBuilder() 129 | .setPrefix(temp_preempted.getBallot().getPrefix() + 1) 130 | .setConductor(ID) 131 | .build(); 132 | //ensure states are not shared 133 | new Scout(Ballot.newBuilder(leaderBallot).build()).start(); 134 | } 135 | break; 136 | } 137 | } 138 | } 139 | 140 | /** 141 | * 这里scouts由leader id + leaderBallot 唯一确定,commanders由leader+leaderBallot+slot唯一确定。 142 | * 对每个不同的scout和commander threads,里面都要有个blocking Q。 143 | * MessageHandler 必须知道msg type,leaderBallot,和proposal,把对应的msg put到对应的Q里。 144 | * 以上保证了reliable。 145 | * 注意:见paper笔记。需要在msg里加额外的from,to fields 146 | * And give scout and commander 一个额外的 int id, use ThreadLocal 147 | */ 148 | private class MessageHandler extends Thread{ 149 | 150 | /** 151 | * message handler must know its leader's id. 152 | * No need to know scouts and commanders, their IDs are in message body. 153 | */ 154 | private final int leadLocalId; 155 | 156 | public MessageHandler (int leadLocalId) { 157 | super("MessageHandler"); 158 | this.leadLocalId = leadLocalId; 159 | } 160 | 161 | @Override 162 | public void run() { 163 | while(true) { 164 | 165 | //This statement will be blocked if there is no incoming message. 166 | Paxos message = environment.receive(ID); 167 | 168 | try { 169 | switch (message.getType()) { 170 | case PROPOSE: { 171 | messageQueues.get(leadLocalId).put(message); 172 | break; 173 | } 174 | case P1B: { 175 | int scoutId = message.getP1B().getToScout(); 176 | if (!messageQueues.containsKey(scoutId)) break; 177 | messageQueues.get(scoutId).put(message); 178 | break; 179 | } 180 | case P2B: { 181 | int commanderId = message.getP2B().getToCommander(); 182 | if (!messageQueues.containsKey(commanderId)) break; 183 | messageQueues.get(commanderId).put(message); 184 | break; 185 | } 186 | } 187 | }catch (InterruptedException e){ 188 | e.printStackTrace(); 189 | } 190 | } 191 | } 192 | 193 | } 194 | 195 | /** 196 | * inner class Scout 197 | * inter-thread communication -- use message queue 198 | * do synod phase 1 199 | */ 200 | private class Scout extends Thread{ 201 | 202 | //Paxos related states, must ensure not shared. 203 | private final Set pValueSet; 204 | private final Ballot scoutBallot; 205 | private final List waitforAccetpors; 206 | 207 | private final BlockingQueue incomingMessages; 208 | 209 | public Scout(Ballot ballot) { 210 | super("Scout in " + ID + " with Ballot " + ballot); 211 | scoutBallot = ballot; 212 | pValueSet = new HashSet<>(); 213 | this.incomingMessages = new ArrayBlockingQueue<>(100); 214 | 215 | //fuck you ThreadLocal, in 216 | waitforAccetpors = new ArrayList<>(environment.getAcceptors()); 217 | } 218 | 219 | @Override 220 | public void run() { 221 | messageQueues.putIfAbsent(ThreadID.get(), this.incomingMessages); 222 | 223 | Paxos p1a = Paxos.newBuilder() 224 | .setType(P1A) 225 | .setP1A(P1a.newBuilder() 226 | .setFromLeader(ID) 227 | .setFromScout(ThreadID.get()) 228 | .setBallot(scoutBallot)) 229 | .build(); 230 | 231 | for(int acceptor : waitforAccetpors){ 232 | environment.send(acceptor, p1a); 233 | } 234 | 235 | try { 236 | while(true){ 237 | 238 | //because of message handler, scout can only get p1b msg 239 | P1b incMsg = incomingMessages.take().getP1B(); 240 | 241 | if(incMsg.getBallot().equals(scoutBallot)){ 242 | pValueSet.addAll(incMsg.getAccepted().getPvalueList()); 243 | //use Integer.valueOf to use internal cache 244 | waitforAccetpors.remove(Integer.valueOf(incMsg.getFrom())); 245 | 246 | if(waitforAccetpors.size() < environment.getAcceptors().size()/2){ 247 | //偷懒实现了,由于inner class知道leader的Q。。。 248 | Leader.this.incomingMessages.put(Paxos.newBuilder() 249 | .setType(ADOPTED) 250 | .setAdopted(Adopted.newBuilder() 251 | .setBallot(scoutBallot) 252 | .setPvalues(Accepted.newBuilder() 253 | .addAllPvalue(pValueSet))) 254 | .build()); 255 | //thread exit 256 | return; 257 | } 258 | } else { 259 | Leader.this.incomingMessages.put(Paxos.newBuilder() 260 | .setType(PREEMPTED) 261 | .setPreempted(Preempted.newBuilder() 262 | .setBallot(incMsg.getBallot())) 263 | .build()); 264 | return; 265 | } 266 | 267 | } 268 | } catch (InterruptedException e) { 269 | e.printStackTrace(); 270 | } finally { 271 | //clean up, remove its message queue from map. 272 | messageQueues.remove(ThreadID.get()); 273 | } 274 | } 275 | } 276 | 277 | /** 278 | * inner class Commander 279 | * do synod phase 2 280 | */ 281 | private class Commander extends Thread{ 282 | private final List waitforAccetpors; 283 | private final PValue pvalue; 284 | 285 | private final BlockingQueue incomingMessages; 286 | 287 | public Commander(PValue pvalue) { 288 | super("Commander in " + ID + " with pvalue " + pvalue); 289 | this.pvalue = pvalue; 290 | waitforAccetpors = new ArrayList<>(environment.getAcceptors()); 291 | incomingMessages = new ArrayBlockingQueue<>(100); 292 | } 293 | 294 | @Override 295 | public void run() { 296 | messageQueues.putIfAbsent(ThreadID.get(), this.incomingMessages); 297 | Paxos p2a = Paxos.newBuilder() 298 | .setType(P2A) 299 | .setP2A(P2a.newBuilder() 300 | .setFromLeader(ID) 301 | .setFromCommander(ThreadID.get()) 302 | .setPvalue(pvalue)) 303 | .build(); 304 | 305 | for(int acceptor : waitforAccetpors){ 306 | //de-register its message queue 307 | environment.send(acceptor, p2a); 308 | } 309 | 310 | try { 311 | while(true){ 312 | //because of message handler, scout can only get p2b msg 313 | P2b incMsg = incomingMessages.take().getP2B(); 314 | 315 | if(incMsg.getBallot().equals(pvalue.getBallot())){ 316 | //use Integer.valueOf to use internal cache 317 | waitforAccetpors.remove(Integer.valueOf(incMsg.getFrom())); 318 | if(waitforAccetpors.size() < environment.getAcceptors().size()/2){ 319 | Paxos decisionToReplicas = Paxos.newBuilder() 320 | .setType(DECISION) 321 | .setDecision(Decision.newBuilder() 322 | .setSlotNum(pvalue.getSlotNum()) 323 | .setC(pvalue.getCmd())) 324 | .build(); 325 | 326 | for(int replica : environment.getReplicas()) { 327 | environment.send(replica, decisionToReplicas); 328 | } 329 | return; 330 | } 331 | } else { 332 | Leader.this.incomingMessages.put(Paxos.newBuilder() 333 | .setType(PREEMPTED) 334 | .setPreempted(Preempted.newBuilder() 335 | .setBallot(incMsg.getBallot())) 336 | .build()); 337 | return; 338 | } 339 | } 340 | } catch (InterruptedException e) { 341 | e.printStackTrace(); 342 | } finally { 343 | messageQueues.remove(ThreadID.get()); 344 | } 345 | } 346 | } 347 | 348 | /** 349 | * really got fucked when call threadlocal get in Thread's constructor. 350 | * The value returned by get() is not for this thread, but for the thread who call the constructor. 351 | * Target thread has not stared when its constructor is being called. 352 | */ 353 | public static class ThreadID { 354 | private static final AtomicInteger nextID = new AtomicInteger(0); 355 | 356 | /** 357 | * 所以这里把ThreadLocal encapsulate的目的是实现auto-increment 358 | * note: 整个class是thread safe的,遵循了书上将的模式 359 | * 360 | * 仔细想想,下面这个obj,其实只有一个,所有threads的threadlocalmap的key都是指向这个obj的 361 | * 知道lead.class不被gc,那么他就是strongly reachable. 362 | */ 363 | private static final ThreadLocal threadID = new ThreadLocal(){ 364 | @Override 365 | protected Integer initialValue() { 366 | return nextID.getAndIncrement(); 367 | } 368 | }; 369 | 370 | public static int get() { 371 | return threadID.get(); 372 | } 373 | } 374 | } -------------------------------------------------------------------------------- /src/java/main/utilities/BallotComparator.java: -------------------------------------------------------------------------------- 1 | package utilities; 2 | 3 | import utilities.PaxosMsgs; 4 | 5 | import java.util.Comparator; 6 | 7 | 8 | public class BallotComparator implements Comparator{ 9 | @Override 10 | public int compare(PaxosMsgs.Ballot o1, PaxosMsgs.Ballot o2) { 11 | String b1 = "" + o1.getPrefix() + o1.getConductor(); 12 | String b2 = "" + o2.getPrefix() + o2.getConductor(); 13 | return b1.compareTo(b2); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/java/main/utilities/Environment.java: -------------------------------------------------------------------------------- 1 | package utilities; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * This interface provide network abstraction. 7 | * Except for methods in this interface, the whole system know nothing about network. 8 | * 9 | * Not assume FIFO, only reliability. 10 | * 11 | * Classes implementing this interface store ID-address mappings, 12 | * which from the config file. 13 | * 14 | * Message boundary design -- 1 byte length 15 | * 16 | */ 17 | public interface Environment { 18 | 19 | public List getReplicas(); 20 | 21 | public List getLeaders(); 22 | 23 | public List getAcceptors(); 24 | 25 | public void send(int toID, PaxosMsgs.Paxos msg); 26 | 27 | public PaxosMsgs.Paxos receive(int id); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/java/main/utilities/SingleNodeEnvironment.java: -------------------------------------------------------------------------------- 1 | package utilities; 2 | 3 | 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.nio.file.Paths; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Scanner; 12 | import java.util.concurrent.*; 13 | 14 | import paxos.Replica; 15 | import paxos.synod.Acceptor; 16 | import paxos.synod.Leader; 17 | import utilities.PaxosMsgs.Paxos; 18 | 19 | /** 20 | * non-distributed mode implementation. 21 | * is used for debugging. 22 | */ 23 | public class SingleNodeEnvironment implements Environment { 24 | 25 | private final List replicas; 26 | private final List leaders; 27 | private final List acceptors; 28 | 29 | private final Map> msgQueueMap; 30 | 31 | private final BlockingQueue result; 32 | 33 | public SingleNodeEnvironment(String config) { 34 | replicas = new ArrayList<>(); 35 | leaders = new ArrayList<>(); 36 | acceptors = new ArrayList<>(); 37 | msgQueueMap = new ConcurrentHashMap<>(); 38 | result = new ArrayBlockingQueue<>(100); 39 | parseConfig(config); 40 | buildMsgQueue(); 41 | } 42 | 43 | private void parseConfig(String filePath) { 44 | Path config = Paths.get(filePath); 45 | try (Scanner sc = new Scanner(Files.newBufferedReader(config))) { 46 | while (sc.hasNextLine()) { 47 | String line = sc.nextLine(); 48 | if (line.length() == 0 || line.charAt(0) == '*') continue; 49 | char role = line.charAt(0); 50 | int id = Integer.parseInt(line.substring(2)); 51 | 52 | switch (role) { 53 | case 'R': { 54 | replicas.add(id); 55 | break; 56 | } 57 | case 'L': { 58 | leaders.add(id); 59 | break; 60 | } 61 | case 'A': { 62 | acceptors.add(id); 63 | break; 64 | } 65 | } 66 | } 67 | } catch (IOException e) { 68 | System.out.println("Cannot parse config file"); 69 | e.printStackTrace(); 70 | } 71 | } 72 | 73 | private void buildMsgQueue() { 74 | for (int i : replicas) { 75 | msgQueueMap.put(i, new ArrayBlockingQueue<>(100)); 76 | } 77 | for (int i : leaders) { 78 | msgQueueMap.put(i, new ArrayBlockingQueue<>(100)); 79 | } 80 | for (int i : acceptors) { 81 | msgQueueMap.put(i, new ArrayBlockingQueue<>(100)); 82 | } 83 | } 84 | 85 | @Override 86 | public List getReplicas() { 87 | return replicas; 88 | } 89 | 90 | @Override 91 | public List getLeaders() { 92 | return leaders; 93 | } 94 | 95 | @Override 96 | public List getAcceptors() { 97 | return acceptors; 98 | } 99 | 100 | @Override 101 | public void send(int toID, PaxosMsgs.Paxos msg) { 102 | try { 103 | msgQueueMap.get(toID).put(msg); 104 | } catch (InterruptedException e) { 105 | e.printStackTrace(); 106 | } 107 | } 108 | 109 | @Override 110 | public Paxos receive(int who) { 111 | //todo null is bad, need to define a default Paxos msg 112 | Paxos msg = null; 113 | try { 114 | msg = msgQueueMap.get(who).take(); 115 | } catch (InterruptedException e) { 116 | e.printStackTrace(); 117 | } 118 | return msg; 119 | } 120 | 121 | /** 122 | * needs two args: config file and # of requests 123 | * 124 | * @param args 125 | */ 126 | public static void main(String[] args) { 127 | Environment env = new SingleNodeEnvironment(args[0]); 128 | 129 | //here, using fixedThreadPool is wrong. Need an unbound pool. 130 | // ExecutorService threadPool = Executors.newCachedThreadPool(); 131 | 132 | //start all nodes 133 | for (int i : env.getReplicas()) { 134 | new Replica(i, env).start(); 135 | } 136 | for (int i : env.getAcceptors()) { 137 | new Acceptor(i, env).start(); 138 | } 139 | for (int i : env.getLeaders()) { 140 | new Leader(i, env).start(); 141 | } 142 | 143 | //sending some requests, assume client id is 99 144 | int numOfReq = Integer.parseInt(args[1]); 145 | for (int i = 1; i <= numOfReq; i++) { 146 | Paxos tempMsg = Paxos.newBuilder() 147 | .setType(Paxos.Type.REQUEST) 148 | .setRequest(PaxosMsgs.Request.newBuilder() 149 | .setC(PaxosMsgs.Command.newBuilder() 150 | .setClient(99) 151 | .setCid(i) 152 | .setOperation("Request # " + i))) 153 | .build(); 154 | for (int r : env.getReplicas()) { 155 | env.send(r, tempMsg); 156 | } 157 | 158 | try { 159 | Thread.sleep(1000); 160 | } catch (InterruptedException e) { 161 | e.printStackTrace(); 162 | } 163 | } 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /src/java/main/utilities/message_specification.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package paxos; 3 | 4 | option java_outer_classname = "PaxosMsgs"; 5 | 6 | //wrapper. Add a protocol header to every message 7 | message Paxos { 8 | enum Type { 9 | P1A = 1; 10 | P1B = 2; 11 | P2A = 3; 12 | P2B = 4; 13 | 14 | REQUEST = 5; 15 | RESPONSE = 6; 16 | 17 | PROPOSE = 7; 18 | DECISION = 8; 19 | 20 | ADOPTED = 9; 21 | PREEMPTED = 10; 22 | } 23 | 24 | required Type type = 1; 25 | 26 | optional P1a p1a = 2; 27 | optional P1b p1b = 3; 28 | optional P2a p2a = 4; 29 | optional P2b p2b = 5; 30 | optional Request request = 6; 31 | optional Response response = 7; 32 | optional Propose propose = 8; 33 | optional Decision decision = 9; 34 | optional Adopted adopted = 10; 35 | optional Preempted preempted = 11; 36 | } 37 | 38 | //message in synod 39 | message P1a { 40 | required int32 fromLeader = 1; 41 | required int32 fromScout = 2; 42 | required Ballot ballot = 3; 43 | } 44 | 45 | message P1b { 46 | required int32 from = 1; 47 | required int32 toScout = 2; 48 | required Ballot ballot = 3; 49 | required Accepted accepted = 4; 50 | } 51 | 52 | message P2a { 53 | required int32 fromLeader = 1; 54 | required int32 fromCommander = 2; 55 | required PValue pvalue = 3; 56 | } 57 | 58 | message P2b { 59 | required int32 from = 1; 60 | required int32 toCommander = 2; 61 | required Ballot ballot = 3; 62 | } 63 | 64 | //message between replicas and clients 65 | message Request { 66 | required Command c = 1; 67 | } 68 | 69 | message Response { 70 | required int32 cid = 1; 71 | required string result = 2; 72 | } 73 | 74 | //messages between replicas and synod 75 | message Propose { 76 | required int32 slot_in = 1; 77 | required Command c = 2; 78 | } 79 | 80 | message Decision { 81 | required int32 slotNum = 1; 82 | required Command c = 2; 83 | } 84 | 85 | //messages for IPC in leader 86 | message Adopted { 87 | required Ballot ballot = 1; 88 | required Accepted pvalues = 2; 89 | } 90 | 91 | message Preempted { 92 | required Ballot ballot = 1; 93 | } 94 | 95 | 96 | 97 | 98 | 99 | //helper structure 100 | message Ballot { 101 | required int32 prefix = 1; 102 | required int32 conductor = 2; 103 | 104 | } 105 | 106 | message PValue { 107 | required Ballot ballot = 1; 108 | required int32 slotNum = 2; 109 | required Command cmd = 3; 110 | 111 | } 112 | 113 | message Accepted { 114 | repeated PValue pvalue = 1; 115 | } 116 | 117 | message Command { 118 | required int32 client = 1; 119 | required int32 cid = 2; 120 | required string operation = 3; 121 | } --------------------------------------------------------------------------------