io.scalecube.cluster.Cluster Maven / Gradle / Ivy
package io.scalecube.cluster;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import io.scalecube.cluster.fdetector.FailureDetector;
import io.scalecube.cluster.gossip.GossipProtocol;
import io.scalecube.cluster.gossip.IGossipProtocol;
import io.scalecube.transport.Message;
import io.scalecube.transport.Transport;
import io.scalecube.transport.TransportAddress;
import io.scalecube.transport.TransportEndpoint;
import com.google.common.base.Function;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.schedulers.Schedulers;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;
/**
* Main ICluster implementation.
*
* @author Anton Kharenko
*/
public final class Cluster implements ICluster {
private static final Logger LOGGER = LoggerFactory.getLogger(Cluster.class);
private enum State {
INSTANTIATED, JOINING, JOINED, LEAVING, STOPPED
}
// Cluster config
private final String memberId;
private final ClusterConfiguration config;
// Cluster components
private final Transport transport;
private final FailureDetector failureDetector;
private final GossipProtocol gossipProtocol;
private final ClusterMembership clusterMembership;
// Cluster state
private final AtomicReference state;
private Cluster(ClusterConfiguration config) {
checkNotNull(config);
checkNotNull(config.transportSettings);
checkNotNull(config.gossipProtocolSettings);
checkNotNull(config.failureDetectorSettings);
checkNotNull(config.clusterMembershipSettings);
this.config = config;
// Build local endpoint
memberId = config.memberId != null ? config.memberId : UUID.randomUUID().toString();
TransportEndpoint localTransportEndpoint = TransportEndpoint.from(memberId, TransportAddress.localTcp(config.port));
// Build transport
transport = Transport.newInstance(localTransportEndpoint, config.transportSettings);
// Build gossip protocol component
gossipProtocol = new GossipProtocol(localTransportEndpoint);
gossipProtocol.setTransport(transport);
gossipProtocol.setMaxGossipSent(config.gossipProtocolSettings.getMaxGossipSent());
gossipProtocol.setGossipTime(config.gossipProtocolSettings.getGossipTime());
gossipProtocol.setMaxEndpointsToSelect(config.gossipProtocolSettings.getMaxEndpointsToSelect());
// Build failure detector component
failureDetector = new FailureDetector(localTransportEndpoint, Schedulers.from(transport.getEventExecutor()));
failureDetector.setTransport(transport);
failureDetector.setPingTime(config.failureDetectorSettings.getPingTime());
failureDetector.setPingTimeout(config.failureDetectorSettings.getPingTimeout());
failureDetector.setMaxEndpointsToSelect(config.failureDetectorSettings.getMaxEndpointsToSelect());
// Build cluster membership component
clusterMembership = new ClusterMembership(localTransportEndpoint, Schedulers.from(transport.getEventExecutor()));
clusterMembership.setFailureDetector(failureDetector);
clusterMembership.setGossipProtocol(gossipProtocol);
clusterMembership.setTransport(transport);
clusterMembership.setLocalMetadata(config.metadata);
clusterMembership.setSeedMembers(config.seedMembers);
clusterMembership.setSyncTime(config.clusterMembershipSettings.getSyncTime());
clusterMembership.setSyncTimeout(config.clusterMembershipSettings.getSyncTimeout());
clusterMembership.setMaxSuspectTime(config.clusterMembershipSettings.getMaxSuspectTime());
clusterMembership.setMaxShutdownTime(config.clusterMembershipSettings.getMaxShutdownTime());
clusterMembership.setSyncGroup(config.clusterMembershipSettings.getSyncGroup());
// Initial state
this.state = new AtomicReference<>(State.INSTANTIATED);
LOGGER.info("Cluster instance '{}' created with configuration: {}", memberId, config);
}
public static Cluster newInstance() {
return newInstance(ClusterConfiguration.newInstance());
}
public static Cluster newInstance(int port) {
return newInstance(ClusterConfiguration.newInstance().port(port));
}
public static Cluster newInstance(int port, String seedMembers) {
return newInstance(ClusterConfiguration.newInstance().port(port).seedMembers(seedMembers));
}
public static Cluster newInstance(String memberId, int port, String seedMembers) {
return newInstance(ClusterConfiguration.newInstance().memberId(memberId).port(port).seedMembers(seedMembers));
}
public static Cluster newInstance(ClusterConfiguration config) {
return new Cluster(config);
}
@Override
public void send(ClusterMember member, Message message) {
checkJoinedState();
transport.send(member.endpoint(), message);
}
@Override
public void send(ClusterMember member, Message message, SettableFuture promise) {
checkJoinedState();
transport.send(member.endpoint(), message, promise);
}
@Override
public Observable listen() {
checkJoinedState();
return transport.listen();
}
@Override
public IGossipProtocol gossip() {
checkJoinedState();
return gossipProtocol;
}
@Override
public IClusterMembership membership() {
checkJoinedState();
return clusterMembership;
}
@Override
public ListenableFuture join() {
updateClusterState(State.INSTANTIATED, State.JOINING);
LOGGER.info("Cluster instance '{}' joining seed members: {}", memberId, config.seedMembers);
ListenableFuture transportFuture = transport.start();
ListenableFuture clusterFuture = Futures.transform(transportFuture, new AsyncFunction() {
@Override
public ListenableFuture apply(@Nullable Void param) throws Exception {
failureDetector.start();
gossipProtocol.start();
return clusterMembership.start();
}
});
return Futures.transform(clusterFuture, new Function() {
@Override
public ICluster apply(@Nullable Void param) {
updateClusterState(State.JOINING, State.JOINED);
LOGGER.info("Cluster instance '{}' joined cluster of members: {}", memberId, membership().members());
return Cluster.this;
}
});
}
@Override
public ICluster joinAwait() {
try {
return join().get();
} catch (Exception e) {
throw Throwables.propagate(Throwables.getRootCause(e));
}
}
@Override
public ListenableFuture leave() {
updateClusterState(State.JOINED, State.LEAVING);
LOGGER.info("Cluster instance '{}' leaving cluster", memberId);
// Notify cluster members about graceful shutdown of current member
clusterMembership.leave();
// Wait for some time until 'leave' gossip start to spread through the cluster before stopping cluster components
final SettableFuture transportStoppedFuture = SettableFuture.create();
final ScheduledExecutorService stopExecutor = Executors.newSingleThreadScheduledExecutor();
long delay = 3 * gossipProtocol.getGossipTime(); // wait for 3 gossip periods before stopping
stopExecutor.schedule(new Runnable() {
@Override
public void run() {
clusterMembership.stop();
gossipProtocol.stop();
failureDetector.stop();
transport.stop(transportStoppedFuture);
}
}, delay, TimeUnit.MILLISECONDS);
// Update cluster state to terminal state
return Futures.transform(transportStoppedFuture, new Function() {
@Nullable
@Override
public Void apply(Void input) {
stopExecutor.shutdown();
updateClusterState(State.LEAVING, State.STOPPED);
LOGGER.info("Cluster instance '{}' stopped", memberId);
return input;
}
});
}
private void checkJoinedState() {
State currentState = state.get();
checkState(currentState == State.JOINED, "Illegal operation at state %s. Member should be joined to cluster.",
state.get());
}
private void updateClusterState(State expected, State update) {
boolean stateUpdated = state.compareAndSet(expected, update);
checkState(stateUpdated, "Illegal state transition from %s to %s cluster state. Expected state %s.", state.get(),
update, expected);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy