org.infinispan.topology.ClusterCacheStatus Maven / Gradle / Ivy
package org.infinispan.topology;
import static org.infinispan.util.logging.LogFactory.CLUSTER;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.infinispan.commons.CacheException;
import org.infinispan.commons.util.Immutables;
import org.infinispan.distribution.ch.ConsistentHash;
import org.infinispan.distribution.ch.ConsistentHashFactory;
import org.infinispan.globalstate.ScopedPersistentState;
import org.infinispan.lifecycle.ComponentStatus;
import org.infinispan.partitionhandling.AvailabilityMode;
import org.infinispan.partitionhandling.impl.AvailabilityStrategy;
import org.infinispan.partitionhandling.impl.AvailabilityStrategyContext;
import org.infinispan.remoting.transport.Address;
import org.infinispan.remoting.transport.Transport;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
/**
* Keeps track of a cache's status: members, current/pending consistent hashes, and rebalance status
*
* @author Dan Berindei
* @since 5.2
*/
public class ClusterCacheStatus implements AvailabilityStrategyContext {
// The HotRod client starts with topology 0, so we start with 1 to force an update
public static final int INITIAL_TOPOLOGY_ID = 1;
public static final int INITIAL_REBALANCE_ID = 1;
private static final Log log = LogFactory.getLog(ClusterCacheStatus.class);
private static boolean trace = log.isTraceEnabled();
private final String cacheName;
private final AvailabilityStrategy availabilityStrategy;
private final ClusterTopologyManager clusterTopologyManager;
private final PersistentUUIDManager persistentUUIDManager;
private Transport transport;
// Minimal cache clustering configuration
private volatile CacheJoinInfo joinInfo;
// Cache members, some of which may not have received state yet
private volatile List expectedMembers;
// Capacity factors for all the members
private volatile Map capacityFactors;
// Cache members that have not yet received state. Always included in the members list.
private volatile List joiners;
// Persistent state (if it exists)
private Optional persistentState;
// Cache topology. Its consistent hashes contain only members that did receive/are receiving state
// The members of both consistent hashes must be included in the members list.
private volatile CacheTopology currentTopology;
private volatile CacheTopology stableTopology;
private volatile AvailabilityMode availabilityMode = AvailabilityMode.AVAILABLE;
private volatile List queuedRebalanceMembers;
private volatile boolean rebalancingEnabled = true;
private volatile RebalanceConfirmationCollector rebalanceConfirmationCollector;
private ComponentStatus status;
public ClusterCacheStatus(String cacheName, AvailabilityStrategy availabilityStrategy,
ClusterTopologyManager clusterTopologyManager, Transport transport, Optional state, PersistentUUIDManager persistentUUIDManager) {
this.cacheName = cacheName;
this.availabilityStrategy = availabilityStrategy;
this.clusterTopologyManager = clusterTopologyManager;
this.transport = transport;
this.persistentState = state;
this.currentTopology = null;
this.stableTopology = null;
this.expectedMembers = Collections.emptyList();
this.capacityFactors = Collections.emptyMap();
this.joiners = Collections.emptyList();
this.persistentUUIDManager = persistentUUIDManager;
state.ifPresent(scopedPersistentState -> {
rebalancingEnabled = false;
availabilityMode = AvailabilityMode.DEGRADED_MODE;
});
status = ComponentStatus.INSTANTIATED;
if (trace) {
log.tracef("Cache %s initialized. Persisted state? %s", cacheName, persistentState.isPresent());
}
}
public CacheJoinInfo getJoinInfo() {
return joinInfo;
}
@Override
public List getExpectedMembers() {
return expectedMembers;
}
@Override
public void queueRebalance(List newMembers) {
synchronized (this) {
if (newMembers != null && !newMembers.isEmpty()) {
log.debugf("Queueing rebalance for cache %s with members %s", cacheName, newMembers);
queuedRebalanceMembers = newMembers;
startQueuedRebalance();
}
}
}
public boolean isTotalOrder() {
return joinInfo.isTotalOrder();
}
public boolean isDistributed() {
return joinInfo.isDistributed();
}
public Map getCapacityFactors() {
return capacityFactors;
}
@Override
public CacheTopology getCurrentTopology() {
return currentTopology;
}
@Override
public CacheTopology getStableTopology() {
return stableTopology;
}
@Override
public AvailabilityMode getAvailabilityMode() {
return availabilityMode;
}
@Override
public void updateAvailabilityMode(List actualMembers, AvailabilityMode newAvailabilityMode,
boolean cancelRebalance) {
synchronized (this) {
boolean modeChanged = setAvailabilityMode(newAvailabilityMode);
if (modeChanged || !actualMembers.equals(currentTopology.getActualMembers())) {
log.debugf("Updating availability for cache %s to %s", cacheName, newAvailabilityMode);
ConsistentHash newPendingCH = currentTopology.getPendingCH();
if (cancelRebalance) {
newPendingCH = null;
if (isRebalanceInProgress()) {
removeRebalanceConfirmationCollector();
}
}
CacheTopology newTopology = new CacheTopology(currentTopology.getTopologyId() + 1,
currentTopology.getRebalanceId(), currentTopology.getCurrentCH(), newPendingCH, actualMembers, persistentUUIDManager.mapAddresses(actualMembers));
setCurrentTopology(newTopology);
clusterTopologyManager.broadcastTopologyUpdate(cacheName, newTopology, newAvailabilityMode,
isTotalOrder(), isDistributed());
}
}
}
@Override
public void updateTopologiesAfterMerge(CacheTopology currentTopology, CacheTopology stableTopology, AvailabilityMode availabilityMode) {
// This method must be called while holding the lock anyway
synchronized (this) {
log.debugf("Updating topologies after merge for cache %s, current topology = %s, stable topology = %s, " +
"availability mode = %s",
cacheName, currentTopology, stableTopology, availabilityMode);
this.currentTopology = currentTopology;
this.stableTopology = stableTopology;
this.availabilityMode = availabilityMode;
if (currentTopology != null) {
clusterTopologyManager.broadcastTopologyUpdate(cacheName, currentTopology, availabilityMode, isTotalOrder(), isDistributed());
}
if (stableTopology != null) {
clusterTopologyManager.broadcastStableTopologyUpdate(cacheName, stableTopology, isTotalOrder(),
isDistributed());
}
}
}
/**
* @return {@code true} if the joiner was not already a member, {@code false} otherwise
*/
private boolean addMember(Address joiner, CacheJoinInfo joinInfo) {
synchronized (this) {
if (expectedMembers.contains(joiner)) {
return false;
}
// Validate if the member is allowed to join
if (persistentState.isPresent()) {
if (!joinInfo.getPersistentStateChecksum().isPresent()) {
if (status == ComponentStatus.INSTANTIATED) {
throw log.nodeWithoutPersistentStateJoiningCacheWithState(joiner, cacheName);
}
} else if (persistentState.get().getChecksum() != joinInfo.getPersistentStateChecksum().get()){
throw log.nodeWithIncompatibleStateJoiningCache(joiner, cacheName);
}
} else {
if (joinInfo.getPersistentStateChecksum().isPresent()) {
throw log.nodeWithPersistentStateJoiningClusterWithoutState(joiner, cacheName);
}
}
if (this.joinInfo == null) {
this.joinInfo = joinInfo;
}
HashMap newCapacityFactors = new HashMap(capacityFactors);
newCapacityFactors.put(joiner, joinInfo.getCapacityFactor());
capacityFactors = Immutables.immutableMapWrap(newCapacityFactors);
expectedMembers = immutableAdd(expectedMembers, joiner);
persistentUUIDManager.addPersistentAddressMapping(joiner, joinInfo.getPersistentUUID());
joiners = immutableAdd(joiners, joiner);
if (trace) log.tracef("Added joiner %s to cache %s with persistent uuid %s: members = %s, joiners = %s", joiner, cacheName,
joinInfo.getPersistentUUID(), expectedMembers, joiners);
return true;
}
}
/**
* @return {@code true} if the leaver was a member, {@code false} otherwise
*/
private boolean removeMember(Address leaver) {
synchronized (this) {
if (!expectedMembers.contains(leaver)) {
if (trace) log.tracef("Trying to remove node %s from cache %s, but it is not a member: " +
"members = %s", leaver, cacheName, expectedMembers);
return false;
}
expectedMembers = immutableRemove(expectedMembers, leaver);
HashMap newCapacityFactors = new HashMap(capacityFactors);
newCapacityFactors.remove(leaver);
capacityFactors = Immutables.immutableMapWrap(newCapacityFactors);
joiners = immutableRemove(joiners, leaver);
if (trace) log.tracef("Removed node %s from cache %s: members = %s, joiners = %s", leaver,
cacheName, expectedMembers, joiners);
return true;
}
}
/**
* @return {@code true} if the members list has changed, {@code false} otherwise
*/
private boolean retainMembers(List newClusterMembers) {
synchronized (this) {
if (newClusterMembers.containsAll(expectedMembers)) {
if (trace) log.tracef("Cluster members updated for cache %s, no abrupt leavers detected: " +
"cache members = %s. Existing members = %s", cacheName, newClusterMembers, expectedMembers);
return false;
}
expectedMembers = immutableRetainAll(expectedMembers, newClusterMembers);
joiners = immutableRetainAll(joiners, newClusterMembers);
if (trace) log.tracef("Cluster members updated for cache %s: members = %s, joiners = %s", cacheName,
expectedMembers, joiners);
return true;
}
}
private void setCurrentTopology(CacheTopology newTopology) {
synchronized (this) {
this.currentTopology = newTopology;
// update the joiners list
if (newTopology != null) {
joiners = immutableRemoveAll(expectedMembers, newTopology.getCurrentCH().getMembers());
}
if (trace) log.tracef("Cache %s topology updated: %s, members = %s, joiners = %s",
cacheName, currentTopology, expectedMembers, joiners);
if (newTopology != null) {
newTopology.logRoutingTableInformation();
}
}
}
private void setStableTopology(CacheTopology newTopology) {
synchronized (this) {
this.stableTopology = newTopology;
if (trace) log.tracef("Cache %s stable topology updated: members = %s, joiners = %s, topology = %s",
cacheName, expectedMembers, joiners, newTopology);
}
}
private boolean needConsistentHashUpdate() {
// The list of current members is always included in the list of pending members,
// so we only need to check one list.
// Also returns false if both CHs are null
return !expectedMembers.equals(currentTopology.getMembers());
}
private List pruneInvalidMembers(List possibleMembers) {
return immutableRetainAll(possibleMembers, expectedMembers);
}
public boolean isRebalanceInProgress() {
return rebalanceConfirmationCollector != null;
}
public RebalancingStatus getRebalancingStatus() {
if (!isRebalanceEnabled()) {
return RebalancingStatus.SUSPENDED;
} else if (isRebalanceInProgress()) {
return RebalancingStatus.IN_PROGRESS;
} else if (queuedRebalanceMembers != null) {
return RebalancingStatus.PENDING;
} else {
return RebalancingStatus.COMPLETE;
}
}
/**
* @return {@code true} if a rebalance was started, {@code false} if a rebalance was already in progress
*/
private boolean initRebalanceConfirmationCollector(CacheTopology newTopology) {
synchronized (this) {
if (rebalanceConfirmationCollector != null)
return false;
rebalanceConfirmationCollector = new RebalanceConfirmationCollector(cacheName, newTopology.getTopologyId(),
newTopology.getMembers());
return true;
}
}
public void doConfirmRebalance(Address member, int receivedTopologyId) throws Exception {
synchronized (this) {
if (rebalanceConfirmationCollector == null) {
throw new CacheException(String.format("Received invalid rebalance confirmation from %s " +
"for cache %s, we don't have a rebalance in progress", member, cacheName));
}
boolean rebalanceCompleted = rebalanceConfirmationCollector.confirmRebalance(member, receivedTopologyId);
if (rebalanceCompleted) {
endRebalance();
}
}
}
/**
* Should be called after the members list was updated in any other way ({@link #removeMember(Address)},
* {@link #retainMembers} etc.)
*
* @return {@code true} if the rebalance was confirmed with this update, {@code false} if more confirmations
* are needed or if the rebalance was already confirmed in another way (e.g. the last member confirmed)
*/
private boolean updateRebalanceMembers() {
synchronized (this) {
if (rebalanceConfirmationCollector == null)
return false;
// We rely on the AvailabilityStrategy updating the current topology beforehand.
return rebalanceConfirmationCollector.updateMembers(currentTopology.getMembers());
}
}
public void doHandleClusterView() throws Exception {
synchronized (this) {
// TODO Clean up ClusterCacheStatus instances once they no longer have any members
if (currentTopology == null)
return;
List newClusterMembers = transport.getMembers();
boolean cacheMembersModified = retainMembers(newClusterMembers);
availabilityStrategy.onClusterViewChange(this, newClusterMembers);
if (cacheMembersModified) {
boolean rebalanceCompleted = updateRebalanceMembers();
if (rebalanceCompleted) {
endRebalance();
}
}
}
}
private void endRebalance() {
synchronized (this) {
removeRebalanceConfirmationCollector();
CacheTopology currentTopology = getCurrentTopology();
if (currentTopology == null) {
log.tracef("Rebalance finished because there are no more members in cache %s", cacheName);
return;
}
int currentTopologyId = currentTopology.getTopologyId();
CLUSTER.clusterWideRebalanceCompleted(cacheName, currentTopologyId);
int newTopologyId = currentTopologyId + 1;
ConsistentHash newCurrentCH = currentTopology.getPendingCH();
CacheTopology newTopology = new CacheTopology(newTopologyId, currentTopology.getRebalanceId(),
newCurrentCH, null, newCurrentCH.getMembers(), persistentUUIDManager.mapAddresses(newCurrentCH.getMembers()));
setCurrentTopology(newTopology);
clusterTopologyManager.broadcastTopologyUpdate(cacheName, newTopology, availabilityMode,
isTotalOrder(), isDistributed());
availabilityStrategy.onRebalanceEnd(this);
startQueuedRebalance();
}
}
private void removeRebalanceConfirmationCollector() {
synchronized (this) {
if (rebalanceConfirmationCollector == null) {
throw new IllegalStateException("Can't end rebalance, there is no rebalance in progress");
}
rebalanceConfirmationCollector = null;
}
}
@Override
public void updateCurrentTopology(List newMembers) {
synchronized (this) {
// The current topology might be null just after a joiner became the coordinator
if (currentTopology == null) {
createInitialCacheTopology();
}
ConsistentHashFactory consistentHashFactory = getJoinInfo().getConsistentHashFactory();
int topologyId = currentTopology.getTopologyId();
int rebalanceId = currentTopology.getRebalanceId();
ConsistentHash currentCH = currentTopology.getCurrentCH();
ConsistentHash pendingCH = currentTopology.getPendingCH();
if (!needConsistentHashUpdate()) {
log.tracef("Cache %s members list was updated, but the cache topology doesn't need to change: %s",
cacheName, currentTopology);
return;
}
if (newMembers.isEmpty()) {
log.tracef("Cache %s no longer has any members, removing topology", cacheName);
setCurrentTopology(null);
setStableTopology(null);
if (isRebalanceInProgress()) {
removeRebalanceConfirmationCollector();
}
// TODO Remove the cache from the cache status map in ClusterTopologyManagerImpl instead
return;
}
List newCurrentMembers = pruneInvalidMembers(currentCH.getMembers());
ConsistentHash newCurrentCH;
List actualMembers;
ConsistentHash newPendingCH = null;
if (newCurrentMembers.isEmpty()) {
// All the current members left, try to replace them with the joiners
log.tracef("All current members left, re-initializing status for cache %s", cacheName);
if (isRebalanceInProgress()) {
removeRebalanceConfirmationCollector();
}
newCurrentMembers = getExpectedMembers();
actualMembers = newCurrentMembers;
newCurrentCH = joinInfo.getConsistentHashFactory().create(
joinInfo.getHashFunction(), joinInfo.getNumOwners(), joinInfo.getNumSegments(),
newCurrentMembers, getCapacityFactors());
} else {
// ReplicatedConsistentHashFactory allocates segments to all its members, so we can't add any members here
newCurrentCH = consistentHashFactory.updateMembers(currentCH, newCurrentMembers, getCapacityFactors());
actualMembers = newCurrentMembers;
if (pendingCH != null) {
List newPendingMembers = pruneInvalidMembers(pendingCH.getMembers());
newPendingCH = consistentHashFactory.updateMembers(pendingCH, newPendingMembers, getCapacityFactors());
actualMembers = newPendingMembers;
}
}
CacheTopology newTopology = new CacheTopology(topologyId + 1, rebalanceId, newCurrentCH, newPendingCH, actualMembers, persistentUUIDManager.mapAddresses(actualMembers));
setCurrentTopology(newTopology);
clusterTopologyManager.broadcastTopologyUpdate(cacheName, newTopology, availabilityMode,
isTotalOrder(), isDistributed());
}
}
private boolean setAvailabilityMode(AvailabilityMode newAvailabilityMode) {
if (newAvailabilityMode == availabilityMode)
return false;
log.tracef("Cache %s availability changed: %s -> %s", cacheName, availabilityMode, newAvailabilityMode);
availabilityMode = newAvailabilityMode;
return true;
}
// Helpers for working with immutable lists
private List immutableAdd(List list, T element) {
List result = new ArrayList(list);
result.add(element);
return Collections.unmodifiableList(result);
}
private List immutableRemove(List list, T element) {
List result = new ArrayList(list);
result.remove(element);
return Collections.unmodifiableList(result);
}
private List immutableRemoveAll(List list, List otherList) {
List result = new ArrayList(list);
result.removeAll(otherList);
return Collections.unmodifiableList(result);
}
private List immutableRetainAll(List list, List otherList) {
List result = new ArrayList(list);
result.retainAll(otherList);
return Collections.unmodifiableList(result);
}
@Override
public String toString() {
return "ClusterCacheStatus{" +
"cacheName='" + cacheName + '\'' +
", members=" + expectedMembers +
", joiners=" + joiners +
", currentTopology=" + currentTopology +
", rebalanceConfirmationCollector=" + rebalanceConfirmationCollector +
'}';
}
public void doMergePartitions(Map statusResponses) {
synchronized (this) {
try {
if (statusResponses.isEmpty()) {
throw new IllegalArgumentException("Should have at least one current topology");
}
HashMap joinInfos = new HashMap<>();
Set currentTopologies = new HashSet<>();
Set stableTopologies = new HashSet<>();
for (Map.Entry e : statusResponses.entrySet()) {
Address sender = e.getKey();
CacheStatusResponse response = e.getValue();
joinInfos.put(sender, response.getCacheJoinInfo());
if (response.getCacheTopology() != null) {
currentTopologies.add(response.getCacheTopology());
}
if (response.getStableTopology() != null) {
stableTopologies.add(response.getStableTopology());
}
}
log.debugf("Recovered %d partition(s) for cache %s: %s", currentTopologies.size(), cacheName, currentTopologies);
recoverMembers(joinInfos, currentTopologies, stableTopologies);
// TODO Should automatically detect when the coordinator has left and there is only one partition
// and continue any in-progress rebalance without resetting the cache topology.
availabilityStrategy.onPartitionMerge(this, statusResponses.values());
} catch (Exception e) {
log.failedToRecoverCacheState(cacheName, e);
}
}
}
private void recoverMembers(Map joinInfos,
Collection currentTopologies, Collection stableTopologies) {
expectedMembers = Collections.emptyList();
// Try to preserve the member order at least for the first partition
for (CacheTopology topology : stableTopologies) {
addMembers(topology.getMembers(), joinInfos);
}
for (CacheTopology topology : currentTopologies) {
addMembers(topology.getMembers(), joinInfos);
}
// Add the joiners that are not part of any topology at the end
for (Map.Entry e : joinInfos.entrySet()) {
if (!expectedMembers.contains(e.getKey())) {
addMember(e.getKey(), e.getValue());
}
}
}
private void addMembers(Collection membersToAdd, Map joinInfos) {
for (Address member : membersToAdd) {
if (!expectedMembers.contains(member)) {
CacheJoinInfo joinInfo = joinInfos.get(member);
// Some members of the stable/current topology may not be members any more
if (joinInfo != null) {
addMember(member, joinInfo);
}
}
}
}
@Override
public String getCacheName() {
return cacheName;
}
public CacheStatusResponse doJoin(Address joiner, CacheJoinInfo joinInfo) throws Exception {
boolean isFirstMember;
CacheTopology topologyBeforeRebalance;
synchronized (this) {
isFirstMember = getCurrentTopology() == null;
boolean memberJoined = addMember(joiner, joinInfo);
if (!isFirstMember && !memberJoined) {
if (trace) log.tracef("Trying to add node %s to cache %s, but it is already a member: " +
"members = %s, joiners = %s", joiner, cacheName, expectedMembers, joiners);
return new CacheStatusResponse(null, currentTopology, stableTopology, availabilityMode);
}
if (status == ComponentStatus.INSTANTIATED) {
if (persistentState.isPresent()) {
if (trace) log.tracef("Node %s joining. Attempting to reform previous cluster", joiner);
// We can only allow this to proceed if we have a complete cluster
CacheTopology topology = restoreCacheTopology(persistentState.get());
if (topology != null) {
// Change our status
status = ComponentStatus.RUNNING;
clusterTopologyManager.broadcastTopologyUpdate(cacheName, topology, availabilityMode, isTotalOrder(), isDistributed());
clusterTopologyManager.broadcastStableTopologyUpdate(cacheName, topology, isTotalOrder(), isDistributed());
return new CacheStatusResponse(null, currentTopology, stableTopology, availabilityMode);
}
} else {
if (isFirstMember) {
// This node was the first to join. We need to install the initial CH
CacheTopology initialTopology = createInitialCacheTopology();
// Change our status
status = ComponentStatus.RUNNING;
// Don't need to broadcast the initial CH update, just return the cache topology to the joiner
// But we do need to broadcast the initial topology as the stable topology
clusterTopologyManager.broadcastStableTopologyUpdate(cacheName, initialTopology, isTotalOrder(), isDistributed());
}
}
}
topologyBeforeRebalance = getCurrentTopology();
// Only trigger availability strategy if we have a topology installed
if (topologyBeforeRebalance != null)
availabilityStrategy.onJoin(this, joiner);
}
return new CacheStatusResponse(null, topologyBeforeRebalance, stableTopology, availabilityMode);
}
protected CacheTopology restoreCacheTopology(ScopedPersistentState state) {
if (trace) log.tracef("Attempting to restore CH for cache %s", cacheName);
ConsistentHash persistedCH = joinInfo.getConsistentHashFactory().fromPersistentState(state).remapAddresses(persistentUUIDManager.persistentUUIDToAddress());
if (persistedCH == null || !getExpectedMembers().containsAll(persistedCH.getMembers())) {
if (trace) log.tracef("Could not restore CH for cache %s, one or more addresses are missing", cacheName);
return null;
}
if (getExpectedMembers().size() > persistedCH.getMembers().size()) {
List extraneousMembers = new ArrayList<>(getExpectedMembers());
extraneousMembers.removeAll(persistedCH.getMembers());
throw log.extraneousMembersJoinRestoredCache(extraneousMembers, cacheName);
}
CacheTopology initialTopology = new CacheTopology(INITIAL_TOPOLOGY_ID, INITIAL_REBALANCE_ID, persistedCH, null, persistedCH.getMembers(), persistentUUIDManager.mapAddresses(persistedCH.getMembers()));
setCurrentTopology(initialTopology);
setStableTopology(initialTopology);
rebalancingEnabled = true;
availabilityMode = AvailabilityMode.AVAILABLE;
return initialTopology;
}
protected CacheTopology createInitialCacheTopology() {
log.tracef("Initializing status for cache %s", cacheName);
List initialMembers = getExpectedMembers();
ConsistentHash initialCH = joinInfo.getConsistentHashFactory().create(
joinInfo.getHashFunction(), joinInfo.getNumOwners(), joinInfo.getNumSegments(),
initialMembers, getCapacityFactors());
CacheTopology initialTopology = new CacheTopology(INITIAL_TOPOLOGY_ID, INITIAL_REBALANCE_ID, initialCH, null, initialMembers, persistentUUIDManager.mapAddresses(initialMembers));
setCurrentTopology(initialTopology);
setStableTopology(initialTopology);
return initialTopology;
}
public boolean doLeave(Address leaver) throws Exception {
synchronized (this) {
if (currentTopology == null)
return false;
boolean actualLeaver = removeMember(leaver);
if (!actualLeaver)
return false;
availabilityStrategy.onGracefulLeave(this, leaver);
boolean rebalanceCompleted = updateRebalanceMembers();
if (rebalanceCompleted) {
endRebalance();
}
return expectedMembers.isEmpty();
}
}
public void startQueuedRebalance() {
synchronized (this) {
if (queuedRebalanceMembers == null) {
// We don't have a queued rebalance. We may need to broadcast a stable topology update
if (stableTopology == null || stableTopology.getTopologyId() < currentTopology.getTopologyId()) {
stableTopology = currentTopology;
log.tracef("Updating stable topology for cache %s: %s", cacheName, stableTopology);
clusterTopologyManager.broadcastStableTopologyUpdate(cacheName, stableTopology, isTotalOrder(), isDistributed());
}
return;
}
CacheTopology cacheTopology = getCurrentTopology();
if (!isRebalanceEnabled()) {
log.tracef("Postponing rebalance for cache %s, rebalancing is disabled", cacheName);
return;
}
if (isRebalanceInProgress()) {
log.tracef("Postponing rebalance for cache %s, there's already a rebalance in progress: %s",
cacheName, rebalanceConfirmationCollector);
return;
}
if (queuedRebalanceMembers.isEmpty()) {
log.tracef("Ignoring request to rebalance cache %s, it doesn't have any member", cacheName);
return;
}
List newMembers = new ArrayList(queuedRebalanceMembers);
queuedRebalanceMembers = null;
log.tracef("Rebalancing consistent hash for cache %s, members are %s", cacheName, newMembers);
if (cacheTopology == null) {
createInitialCacheTopology();
return;
}
int newTopologyId = cacheTopology.getTopologyId() + 1;
int newRebalanceId = cacheTopology.getRebalanceId() + 1;
ConsistentHash currentCH = cacheTopology.getCurrentCH();
if (currentCH == null) {
// There was one node in the cache before, and it left after the rebalance was triggered
// but before the rebalance actually started.
log.tracef("Ignoring request to rebalance cache %s, it doesn't have a consistent hash", cacheName);
return;
}
if (!expectedMembers.containsAll(newMembers)) {
newMembers.removeAll(expectedMembers);
log.tracef("Ignoring request to rebalance cache %s, we have new leavers: %s", cacheName, newMembers);
return;
}
ConsistentHashFactory chFactory = getJoinInfo().getConsistentHashFactory();
// This update will only add the joiners to the CH, we have already checked that we don't have leavers
ConsistentHash updatedMembersCH = chFactory.updateMembers(currentCH, newMembers, getCapacityFactors());
ConsistentHash balancedCH = chFactory.rebalance(updatedMembersCH);
if (balancedCH.equals(currentCH)) {
log.tracef("The balanced CH is the same as the current CH, not rebalancing");
return;
}
CacheTopology newTopology = new CacheTopology(newTopologyId, newRebalanceId, currentCH, balancedCH,
balancedCH.getMembers(), persistentUUIDManager.mapAddresses(balancedCH.getMembers()));
log.tracef("Updating cache %s topology for rebalance: %s", cacheName, newTopology);
setCurrentTopology(newTopology);
initRebalanceConfirmationCollector(newTopology);
}
clusterTopologyManager.broadcastRebalanceStart(cacheName, this.getCurrentTopology(), this.isTotalOrder(), this.isDistributed());
}
public boolean isRebalanceEnabled() {
return rebalancingEnabled && clusterTopologyManager.isRebalancingEnabled();
}
public void setRebalanceEnabled(boolean enabled) {
synchronized (this) {
rebalancingEnabled = enabled;
if (rebalancingEnabled) {
log.debugf("Rebalancing is now enabled for cache %s", cacheName);
startQueuedRebalance();
} else {
log.debugf("Rebalancing is now disabled for cache %s", cacheName);
}
}
}
public void forceRebalance() {
queueRebalance(getCurrentTopology().getMembers());
startQueuedRebalance();
}
public void forceAvailabilityMode(AvailabilityMode newAvailabilityMode) {
availabilityStrategy.onManualAvailabilityChange(this, newAvailabilityMode);
}
public void shutdownCache() throws Exception {
synchronized (this) {
if (status == ComponentStatus.RUNNING) {
status = ComponentStatus.STOPPING;
clusterTopologyManager.setRebalancingEnabled(cacheName, false);
clusterTopologyManager.broadcastShutdownCache(cacheName, this.getCurrentTopology(), this.isTotalOrder(), this.isDistributed());
status = ComponentStatus.TERMINATED;
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy