com.yahoo.gondola.Cluster Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of core Show documentation
Show all versions of core Show documentation
Java implementation of Raft
The newest version!
/*
* Copyright 2015, Yahoo Inc.
* Copyrights licensed under the New BSD License.
* See the accompanying LICENSE file for terms.
*/
package com.yahoo.gondola;
import com.yahoo.gondola.core.CoreMember;
import com.yahoo.gondola.core.Message;
import com.yahoo.gondola.core.Peer;
import com.yahoo.gondola.core.Stats;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
public class Cluster implements Stoppable {
final static Logger logger = LoggerFactory.getLogger(Cluster.class);
final Gondola gondola;
final Config config;
final Stats stats;
final String clusterId;
final List peers = new ArrayList<>();
final List members = new ArrayList<>();
Member localMember;
CoreMember cmember;
Cluster(Gondola gondola, String clusterId) throws Exception {
this.gondola = gondola;
this.clusterId = clusterId;
config = gondola.getConfig();
stats = gondola.getStats();
List configMembers = config.getMembersInCluster(clusterId);
List peerIds = configMembers.stream()
.filter(cm -> !cm.hostId.equals(gondola.getHostId()))
.map(cm -> cm.memberId).collect(Collectors.toList());
// First create the local member, because it's needed when creating the remote members.
for (int i = 0; i < configMembers.size(); i++) {
Config.ConfigMember cm = configMembers.get(i);
if (gondola.getHostId().equals(cm.hostId)) {
// Local member
boolean isPrimary = i == 0;
cmember = new CoreMember(gondola, this, cm.memberId, peerIds, isPrimary);
localMember = new Member(gondola, cmember, null);
members.add(localMember);
break;
}
}
if (cmember == null) {
throw new IllegalStateException(String.format("Host id %s not found in %d", config.getIdentifier()));
}
// Create list of peers
for (Peer p : cmember.peers) {
members.add(new Member(gondola, cmember, p));
}
}
public void start() throws Exception {
cmember.start();
}
public void stop() {
cmember.stop();
}
/******************** methods *********************/
/**
* Returns the cluster as provided in the constructor. See Cluster().
*
* @return non-null cluster id.
*/
public String getClusterId() {
return clusterId;
}
/**
* Returns the member with the specified id.
*
* @return null if the member id is not known.
*/
public Member getMember(int id) {
for (Member m : members) {
if (m.getMemberId() == id) {
return m;
}
}
return null;
}
/**
* @return non-null list of members in this cluster.
*/
public List getMembers() {
return members;
}
/**
* Returns the local member of this cluster. The local member is the actual process running on this host.
* The remote members are proxies of members running on other hosts.
*
* @return non-null local member.
*/
public Member getLocalMember() {
return localMember;
}
/**
* Returns the list of non-local members.
*
* @return non-null list of non-local members.
*/
public List getRemoteMembers() {
return members.stream().filter(m -> !m.isLocal()).collect(Collectors.toList());
}
/**
* Returns the current leader.
*
* @return null if the leader is not known.
*/
public Member getLeader() {
return getMember(cmember.getLeaderId());
}
/**
* Forces the local member of this cluster to become the leader.
* Blocks until this member becomes the leader.
* @param timeout -1 means there is no timeout.
*/
public void forceLeader(int timeout) {
long start = System.currentTimeMillis();
while (!cmember.isLeader()) {
try {
cmember.forceLeader();
if (timeout >= 0 && System.currentTimeMillis() - start > timeout) {
break;
}
Thread.sleep(1000);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
public Role getLocalRole() {
return cmember.getRole();
}
/**
* Returns the last saved index for this cluster.
*/
public int getLastSavedIndex() throws Exception {
return cmember.getSavedIndex();
}
/**
* ******************* commands ******************
*/
// TODO: move pool to Gondola, like message pool
Queue pool = new ConcurrentLinkedQueue<>();
/**
* Retrieves a command object from the command pool.
* Blocks until there are commands in the pool.
*
* @return non-null command object
*/
public Command checkoutCommand() throws InterruptedException {
Command command = pool.poll();
if (command == null) {
command = new Command(gondola, this, cmember);
}
return command;
}
/**
* Returns the command back into the command pool.
*
* @param command non-null command that is no longer used.
*/
void checkinCommand(Command command) {
pool.add(command);
}
/**
* Equivalent to getCommittedCommand(index, -1);
*
* @param index must be > 0.
* @return the non-null Command at index.
*/
public Command getCommittedCommand(int index) throws Exception {
return getCommittedCommand(index, -1);
}
/**
* Returns the command at the specified index. This method blocks until index has been committed.
* An empty command can be returned. This is an artifact of the raft protocol to avoid deadlock when
* used with a finite thread pool. Empty commands will be inserted right after a leader election when
* the new leader discovers that it has uncommitted commands.
* The leader inserts an empty command to commit these immediately.
*
* @param index Must be > 0.
* @param timeout Returns after timeout milliseconds, even if the command is not yet available. -1 means there is no timeout.
* @return non-null Command
*/
public Command getCommittedCommand(int index, int timeout) throws Exception {
if (index <= 0) {
throw new IllegalStateException(String.format("Index %d must be > 0", index));
}
Command command = checkoutCommand();
try {
cmember.getCommittedLogEntry(command.ccmd, index, timeout);
return command;
} catch (Exception e) {
command.release();
throw e;
}
}
}