Search in sources :

Example 1 with Quorum

use of io.atomix.protocols.raft.utils.Quorum in project atomix by atomix.

the class CandidateRole method sendVoteRequests.

/**
 * Resets the election timer.
 */
private void sendVoteRequests() {
    raft.checkThread();
    // simply skip the election.
    if (!isRunning()) {
        return;
    }
    // Cancel the current timer task and purge the election timer of cancelled tasks.
    if (currentTimer != null) {
        currentTimer.cancel();
    }
    // When the election timer is reset, increment the current term and
    // restart the election.
    raft.setTerm(raft.getTerm() + 1);
    raft.setLastVotedFor(raft.getCluster().getMember().nodeId());
    final AtomicBoolean complete = new AtomicBoolean();
    final Set<DefaultRaftMember> votingMembers = new HashSet<>(raft.getCluster().getActiveMemberStates().stream().map(RaftMemberContext::getMember).collect(Collectors.toList()));
    // If there are no other members in the cluster, immediately transition to leader.
    if (votingMembers.isEmpty()) {
        log.debug("Single member cluster. Transitioning directly to leader.", raft.getCluster().getMember().nodeId());
        raft.transition(RaftServer.Role.LEADER);
        return;
    }
    // Send vote requests to all nodes. The vote request that is sent
    // to this node will be automatically successful.
    // First check if the quorum is null. If the quorum isn't null then that
    // indicates that another vote is already going on.
    final Quorum quorum = new Quorum(raft.getCluster().getQuorum(), (elected) -> {
        if (!isRunning()) {
            return;
        }
        complete.set(true);
        if (elected) {
            raft.transition(RaftServer.Role.LEADER);
        } else {
            raft.transition(RaftServer.Role.FOLLOWER);
        }
    });
    Duration delay = raft.getElectionTimeout().plus(Duration.ofMillis(random.nextInt((int) raft.getElectionTimeout().toMillis())));
    currentTimer = raft.getThreadContext().schedule(delay, () -> {
        if (!complete.get()) {
            // When the election times out, clear the previous majority vote
            // check and restart the election.
            log.debug("Election timed out");
            quorum.cancel();
            sendVoteRequests();
            log.debug("Restarted election");
        }
    });
    // First, load the last log entry to get its term. We load the entry
    // by its index since the index is required by the protocol.
    final Indexed<RaftLogEntry> lastEntry = raft.getLogWriter().getLastEntry();
    final long lastTerm;
    if (lastEntry != null) {
        lastTerm = lastEntry.entry().term();
    } else {
        lastTerm = 0;
    }
    log.debug("Requesting votes for term {}", raft.getTerm());
    // of the cluster and vote each member for a vote.
    for (DefaultRaftMember member : votingMembers) {
        log.debug("Requesting vote from {} for term {}", member, raft.getTerm());
        VoteRequest request = VoteRequest.builder().withTerm(raft.getTerm()).withCandidate(raft.getCluster().getMember().nodeId()).withLastLogIndex(lastEntry != null ? lastEntry.index() : 0).withLastLogTerm(lastTerm).build();
        raft.getProtocol().vote(member.nodeId(), request).whenCompleteAsync((response, error) -> {
            raft.checkThread();
            if (isRunning() && !complete.get()) {
                if (error != null) {
                    log.warn(error.getMessage());
                    quorum.fail();
                } else {
                    if (response.term() > raft.getTerm()) {
                        log.debug("Received greater term from {}", member);
                        raft.setTerm(response.term());
                        complete.set(true);
                        raft.transition(RaftServer.Role.FOLLOWER);
                    } else if (!response.voted()) {
                        log.debug("Received rejected vote from {}", member);
                        quorum.fail();
                    } else if (response.term() != raft.getTerm()) {
                        log.debug("Received successful vote for a different term from {}", member);
                        quorum.fail();
                    } else {
                        log.debug("Received successful vote from {}", member);
                        quorum.succeed();
                    }
                }
            }
        }, raft.getThreadContext());
    }
}
Also used : AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) Quorum(io.atomix.protocols.raft.utils.Quorum) DefaultRaftMember(io.atomix.protocols.raft.cluster.impl.DefaultRaftMember) RaftMemberContext(io.atomix.protocols.raft.cluster.impl.RaftMemberContext) VoteRequest(io.atomix.protocols.raft.protocol.VoteRequest) Duration(java.time.Duration) HashSet(java.util.HashSet) RaftLogEntry(io.atomix.protocols.raft.storage.log.entry.RaftLogEntry)

Example 2 with Quorum

use of io.atomix.protocols.raft.utils.Quorum in project atomix by atomix.

the class FollowerRole method sendPollRequests.

/**
 * Polls all members of the cluster to determine whether this member should transition to the CANDIDATE state.
 */
private void sendPollRequests() {
    // Set a new timer within which other nodes must respond in order for this node to transition to candidate.
    heartbeatTimer = raft.getThreadContext().schedule(raft.getElectionTimeout(), () -> {
        log.debug("Failed to poll a majority of the cluster in {}", raft.getElectionTimeout());
        resetHeartbeatTimeout();
    });
    // Create a quorum that will track the number of nodes that have responded to the poll request.
    final AtomicBoolean complete = new AtomicBoolean();
    final Set<DefaultRaftMember> votingMembers = raft.getCluster().getActiveMemberStates().stream().map(RaftMemberContext::getMember).collect(Collectors.toSet());
    // If there are no other members in the cluster, immediately transition to leader.
    if (votingMembers.isEmpty()) {
        raft.transition(RaftServer.Role.CANDIDATE);
        return;
    }
    final Quorum quorum = new Quorum(raft.getCluster().getQuorum(), (elected) -> {
        // If a majority of the cluster indicated they would vote for us then transition to candidate.
        complete.set(true);
        if (elected) {
            raft.transition(RaftServer.Role.CANDIDATE);
        } else {
            resetHeartbeatTimeout();
        }
    });
    // First, load the last log entry to get its term. We load the entry
    // by its index since the index is required by the protocol.
    final Indexed<RaftLogEntry> lastEntry = raft.getLogWriter().getLastEntry();
    final long lastTerm;
    if (lastEntry != null) {
        lastTerm = lastEntry.entry().term();
    } else {
        lastTerm = 0;
    }
    log.debug("Polling members {}", votingMembers);
    // of the cluster and vote each member for a vote.
    for (DefaultRaftMember member : votingMembers) {
        log.debug("Polling {} for next term {}", member, raft.getTerm() + 1);
        PollRequest request = PollRequest.builder().withTerm(raft.getTerm()).withCandidate(raft.getCluster().getMember().nodeId()).withLastLogIndex(lastEntry != null ? lastEntry.index() : 0).withLastLogTerm(lastTerm).build();
        raft.getProtocol().poll(member.nodeId(), request).whenCompleteAsync((response, error) -> {
            raft.checkThread();
            if (isRunning() && !complete.get()) {
                if (error != null) {
                    log.warn("{}", error.getMessage());
                    quorum.fail();
                } else {
                    if (response.term() > raft.getTerm()) {
                        raft.setTerm(response.term());
                    }
                    if (!response.accepted()) {
                        log.debug("Received rejected poll from {}", member);
                        quorum.fail();
                    } else if (response.term() != raft.getTerm()) {
                        log.debug("Received accepted poll for a different term from {}", member);
                        quorum.fail();
                    } else {
                        log.debug("Received accepted poll from {}", member);
                        quorum.succeed();
                    }
                }
            }
        }, raft.getThreadContext());
    }
}
Also used : AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) Quorum(io.atomix.protocols.raft.utils.Quorum) DefaultRaftMember(io.atomix.protocols.raft.cluster.impl.DefaultRaftMember) PollRequest(io.atomix.protocols.raft.protocol.PollRequest) RaftLogEntry(io.atomix.protocols.raft.storage.log.entry.RaftLogEntry)

Aggregations

DefaultRaftMember (io.atomix.protocols.raft.cluster.impl.DefaultRaftMember)2 RaftLogEntry (io.atomix.protocols.raft.storage.log.entry.RaftLogEntry)2 Quorum (io.atomix.protocols.raft.utils.Quorum)2 AtomicBoolean (java.util.concurrent.atomic.AtomicBoolean)2 RaftMemberContext (io.atomix.protocols.raft.cluster.impl.RaftMemberContext)1 PollRequest (io.atomix.protocols.raft.protocol.PollRequest)1 VoteRequest (io.atomix.protocols.raft.protocol.VoteRequest)1 Duration (java.time.Duration)1 HashSet (java.util.HashSet)1