org.jboss.ejb.client.ClusterNodeSelector Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
/*
* JBoss, Home of Professional Open Source.
* Copyright 2017 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.ejb.client;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import org.jboss.ejb._private.Logs;
import org.wildfly.common.Assert;
/**
* A selector which selects and returns a node from the available nodes in a cluster. Typical usage of a
* {@link ClusterNodeSelector} involve load balancing of calls to various nodes in the cluster.
*
* @author Jaikiran Pai
* @author Wolf Dieter Fink
*/
public interface ClusterNodeSelector {
/**
* Returns a node from among the {@code totalAvailableNodes}, as the target node for Enterprise Beans invocations.
* The selector can decide whether to pick an already connected node (from the passed {@code connectedNodes})
* or decide to select a node to which a connection hasn't yet been established. If a node to which a connection
* hasn't been established is selected then the cluster context will create a connection to it.
*
* @param clusterName the name of the cluster to which the nodes belong (will not be {@code null})
* @param connectedNodes the node names to which a connection has been established (may be empty but will not be {@code null})
* @param totalAvailableNodes all available nodes in the cluster, including connected nodes (will not be empty or {@code null})
* @return the selected node name (must not be {@code null})
*/
String selectNode(final String clusterName, final String[] connectedNodes, final String[] totalAvailableNodes);
/**
* Always use the first available node, regardless of whether it is connected.
*/
ClusterNodeSelector FIRST_AVAILABLE = (clusterName, connectedNodes, totalAvailableNodes) -> totalAvailableNodes[0];
/**
* Always use the first connected node, or fall back to the first available node if none are connected.
*/
ClusterNodeSelector FIRST_CONNECTED = firstConnected(FIRST_AVAILABLE);
/**
* Always use a random connected node, or fall back to a random unconnected node.
*/
ClusterNodeSelector RANDOM_CONNECTED = useRandomConnectedNode(useRandomUnconnectedNode(FIRST_AVAILABLE));
/**
* Use a random available node, regardless of whether it is connected.
*/
ClusterNodeSelector RANDOM = (clusterName, connectedNodes, totalAvailableNodes) -> totalAvailableNodes[ThreadLocalRandom.current().nextInt(totalAvailableNodes.length)];
/**
* Use available nodes in a round-robin fashion, regardless of whether it is connected.
*/
ClusterNodeSelector ROUND_ROBIN = new ClusterNodeSelector() {
private final AtomicInteger count = new AtomicInteger();
public String selectNode(final String clusterName, final String[] connectedNodes, final String[] totalAvailableNodes) {
assert totalAvailableNodes.length != 0;
return totalAvailableNodes[Math.floorMod(count.getAndIncrement(), totalAvailableNodes.length)];
}
};
/**
* A simple default selector which uses {@link #simpleConnectionThresholdRandomSelector(int)} with a minimum of
* 5 connections.
*/
ClusterNodeSelector DEFAULT = simpleConnectionThresholdRandomSelector(5);
/**
* A deployment node selector which check the server name if inside and prefer it if available for selection.
* If no local node is used the DEFAULT will be used and connect a minimum of 5 nodes and select it randomly.
*/
ClusterNodeSelector DEFAULT_PREFER_LOCAL = new ClusterNodeSelector() {
private final String localNodeName = SecurityUtils.getString(SystemProperties.JBOSS_NODE_NAME);
public String selectNode(String clusterName, String[] connectedNodes, String[] totalAvailableNodes) {
// Check if more than one node is available
if (totalAvailableNodes.length > 1 && localNodeName != null) {
for (final String node : totalAvailableNodes) {
if (localNodeName.equals(node)) {
if (Logs.MAIN.isDebugEnabled()) {
Logs.MAIN.debugf("Select local node %s for [cluster: %s]", this.localNodeName, clusterName);
}
return node;
}
}
}
return DEFAULT.selectNode(clusterName, connectedNodes, totalAvailableNodes);
}
};
ClusterNodeSelector RANDOM_PREFER_LOCAL = new ClusterNodeSelector() {
private final String localNodeName = SecurityUtils.getString(SystemProperties.JBOSS_NODE_NAME);
public String selectNode(String clusterName, String[] connectedNodes, String[] totalAvailableNodes) {
// Check if more than one node is available
if (totalAvailableNodes.length > 1 && localNodeName != null) {
for (final String node : totalAvailableNodes) {
if (localNodeName.equals(node)) {
if (Logs.MAIN.isDebugEnabled()) {
Logs.MAIN.debugf("Select local node %s for [cluster: %s]", this.localNodeName, clusterName);
}
return node;
}
}
}
return RANDOM.selectNode(clusterName, connectedNodes, totalAvailableNodes);
}
};
/**
* A simple threshold-based random selector. If the minimum is met, then a random connected node is used, otherwise
* a random available node is used, increasing the total number of connections for future invocations.
*
* @param minimum the minimum number of connected nodes to attempt to acquire
* @return the node selector (not {@code null})
*/
static ClusterNodeSelector simpleConnectionThresholdRandomSelector(int minimum) {
return minimumConnectionThreshold(minimum, RANDOM, useRandomConnectedNode(useRandomUnconnectedNode(FIRST_AVAILABLE)));
}
/**
* Always try to use the first connected node. If no nodes are connected, the fallback is used.
*
* @param fallback the fallback selector (must not be {@code null})
* @return the node selector (not {@code null})
*/
static ClusterNodeSelector firstConnected(ClusterNodeSelector fallback) {
Assert.checkNotNullParam("fallback", fallback);
return (clusterName, connectedNodes, totalAvailableNodes) -> connectedNodes.length > 0 ? connectedNodes[0] : fallback.selectNode(clusterName, connectedNodes, totalAvailableNodes);
}
/**
* Always try to use a random connected node. If no nodes are connected, the fallback is used.
*
* @param fallback the fallback selector (must not be {@code null})
* @return the node selector (not {@code null})
*/
static ClusterNodeSelector useRandomConnectedNode(ClusterNodeSelector fallback) {
Assert.checkNotNullParam("fallback", fallback);
return (clusterName, connectedNodes, totalAvailableNodes) -> {
final int length = connectedNodes.length;
return length > 0 ? connectedNodes[ThreadLocalRandom.current().nextInt(length)] : fallback.selectNode(clusterName, connectedNodes, totalAvailableNodes);
};
}
/**
* Always try to round-robin among connected nodes. If no nodes are connected, the fallback is used. Note
* that the round-robin node count may be shared among multiple node sets, thus certain specific usage patterns
* may defeat the round-robin behavior.
*
* @param fallback the fallback selector (must not be {@code null})
* @return the node selector (not {@code null})
*
* @deprecated will be removed in future version
*/
@Deprecated
static ClusterNodeSelector useRoundRobinConnectedNode(ClusterNodeSelector fallback) {
Assert.checkNotNullParam("fallback", fallback);
return new ClusterNodeSelector() {
private final AtomicInteger count = new AtomicInteger();
public String selectNode(final String clusterName, final String[] connectedNodes, final String[] totalAvailableNodes) {
return connectedNodes.length > 0 ? connectedNodes[Math.floorMod(count.getAndIncrement(), connectedNodes.length)] : fallback.selectNode(clusterName, connectedNodes, totalAvailableNodes);
}
};
}
/**
* Determine the action to take based on a threshold of minimum connections. If the minimum is met, or if
* there are no more available nodes to choose from, the {@code met} selector is used, otherwise the {@code unmet}
* selector is used.
*
* @param minimum the minimum number of connections
* @param unmet the selector to use when the number of connections is below the minimum and there are unconnected nodes (must not be {@code null})
* @param met the selector to use when the number of connections is at or above the minimum or there are no unconnected nodes (must not be {@code null})
* @return the node selector (not {@code null})
*/
static ClusterNodeSelector minimumConnectionThreshold(int minimum, ClusterNodeSelector unmet, ClusterNodeSelector met) {
return (clusterName, connectedNodes, totalAvailableNodes) -> (connectedNodes.length < minimum ? unmet : met).selectNode(clusterName, connectedNodes, totalAvailableNodes);
}
/**
* Always try to use an unconnected node. If all nodes are connected, the fallback is used.
*
* @param fallback the selector to use if all available nodes are connected (must not be {@code null})
* @return the node selector (not {@code null})
*/
static ClusterNodeSelector useRandomUnconnectedNode(ClusterNodeSelector fallback) {
Assert.checkNotNullParam("fallback", fallback);
return (clusterName, connectedNodes, totalAvailableNodes) -> {
if (connectedNodes.length == totalAvailableNodes.length) {
// totalAvailableNodes contains all connectedNodes; if their sizes are equal then all nodes must be connected
return fallback.selectNode(clusterName, connectedNodes, totalAvailableNodes);
}
final HashSet connected = new HashSet<>(connectedNodes.length);
Collections.addAll(connected, connectedNodes);
final ArrayList available = new ArrayList<>(totalAvailableNodes.length);
Collections.addAll(available, totalAvailableNodes);
available.removeAll(connected);
assert ! available.isEmpty();
return available.get(ThreadLocalRandom.current().nextInt(available.size()));
};
}
/**
* Always try to use an unconnected node in a round-robin fashion. If all nodes are connected, the fallback is used.
*
* @param fallback the selector to use if all available nodes are connected (must not be {@code null})
* @return the node selector (not {@code null})
* @deprecated will be removed in future version
*/
@Deprecated
static ClusterNodeSelector useRoundRobinUnconnectedNode(ClusterNodeSelector fallback) {
Assert.checkNotNullParam("fallback", fallback);
return new ClusterNodeSelector() {
private final AtomicInteger count = new AtomicInteger();
public String selectNode(final String clusterName, final String[] connectedNodes, final String[] totalAvailableNodes) {
if (connectedNodes.length == totalAvailableNodes.length) {
// totalAvailableNodes contains all connectedNodes; if their sizes are equal then all nodes must be connected
return fallback.selectNode(clusterName, connectedNodes, totalAvailableNodes);
}
final HashSet connected = new HashSet<>(connectedNodes.length);
Collections.addAll(connected, connectedNodes);
final ArrayList available = new ArrayList<>(totalAvailableNodes.length);
Collections.addAll(available, totalAvailableNodes);
available.removeAll(connected);
assert ! available.isEmpty();
return available.get(Math.floorMod(count.getAndIncrement(), connectedNodes.length));
}
};
}
}