blob: 5bf4690da2c31edbfd610f097e4722ad69406d5b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.solr.client.solrj.routing;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import org.apache.solr.common.StringUtils;
import org.apache.solr.common.cloud.NodesSysPropsCacher;
import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.params.ShardParams;
import org.apache.solr.common.params.SolrParams;
/**
* This comparator makes sure that the given replicas are sorted according to the given list of preferences.
* E.g. If all nodes prefer local cores then a bad/heavily-loaded node will receive less requests from
* healthy nodes. This will help prevent a distributed deadlock or timeouts in all the healthy nodes due
* to one bad node.
*
* Optional final preferenceRule is *not* used for pairwise sorting, but instead defines how "equivalent"
* replicas will be ordered (the base ordering). Defaults to "random"; may specify "stable".
*/
public class NodePreferenceRulesComparator implements Comparator<Object> {
private final NodesSysPropsCacher sysPropsCache;
private final String nodeName;
private final List<PreferenceRule> sortRules;
private final List<PreferenceRule> preferenceRules;
private final String localHostAddress;
private final ReplicaListTransformer baseReplicaListTransformer;
public NodePreferenceRulesComparator(final List<PreferenceRule> preferenceRules, final SolrParams requestParams,
final ReplicaListTransformerFactory defaultRltFactory, final ReplicaListTransformerFactory stableRltFactory) {
this(preferenceRules, requestParams, null, null, null, defaultRltFactory, stableRltFactory);
}
public NodePreferenceRulesComparator(final List<PreferenceRule> preferenceRules, final SolrParams requestParams,
final String nodeName, final String localHostAddress, final NodesSysPropsCacher sysPropsCache,
final ReplicaListTransformerFactory defaultRltFactory, final ReplicaListTransformerFactory stableRltFactory) {
this.sysPropsCache = sysPropsCache;
this.preferenceRules = preferenceRules;
this.nodeName = nodeName;
this.localHostAddress = localHostAddress;
final int maxIdx = preferenceRules.size() - 1;
final PreferenceRule lastRule = preferenceRules.get(maxIdx);
if (!ShardParams.SHARDS_PREFERENCE_REPLICA_BASE.equals(lastRule.name)) {
this.sortRules = preferenceRules;
this.baseReplicaListTransformer = defaultRltFactory.getInstance(null, requestParams, RequestReplicaListTransformerGenerator.RANDOM_RLTF);
} else {
if (maxIdx == 0) {
this.sortRules = null;
} else {
this.sortRules = preferenceRules.subList(0, maxIdx);
}
String[] parts = lastRule.value.split(":", 2);
switch (parts[0]) {
case ShardParams.REPLICA_RANDOM:
this.baseReplicaListTransformer = RequestReplicaListTransformerGenerator.RANDOM_RLTF.getInstance(parts.length == 1 ? null : parts[1], requestParams, null);
break;
case ShardParams.REPLICA_STABLE:
this.baseReplicaListTransformer = stableRltFactory.getInstance(parts.length == 1 ? null : parts[1], requestParams, RequestReplicaListTransformerGenerator.RANDOM_RLTF);
break;
default:
throw new IllegalArgumentException("Invalid base replica order spec");
}
}
}
private static final ReplicaListTransformer NOOP_RLT = (List<?> choices) -> { /* noop */ };
private static final ReplicaListTransformerFactory NOOP_RLTF =
(String configSpec, SolrParams requestParams, ReplicaListTransformerFactory fallback) -> NOOP_RLT;
/**
* For compatibility with tests, which expect this constructor to have no effect on the *base* order.
*/
NodePreferenceRulesComparator(final List<PreferenceRule> sortRules, final SolrParams requestParams) {
this(sortRules, requestParams, NOOP_RLTF, null);
}
public ReplicaListTransformer getBaseReplicaListTransformer() {
return baseReplicaListTransformer;
}
@Override
public int compare(Object left, Object right) {
if (this.sortRules != null) {
for (PreferenceRule preferenceRule: this.sortRules) {
final boolean lhs;
final boolean rhs;
switch (preferenceRule.name) {
case ShardParams.SHARDS_PREFERENCE_REPLICA_TYPE:
lhs = hasReplicaType(left, preferenceRule.value);
rhs = hasReplicaType(right, preferenceRule.value);
break;
case ShardParams.SHARDS_PREFERENCE_REPLICA_LOCATION:
lhs = hasCoreUrlPrefix(left, preferenceRule.value);
rhs = hasCoreUrlPrefix(right, preferenceRule.value);
break;
case ShardParams.SHARDS_PREFERENCE_REPLICA_LEADER:
lhs = hasLeaderStatus(left, preferenceRule.value);
rhs = hasLeaderStatus(right, preferenceRule.value);
break;
case ShardParams.SHARDS_PREFERENCE_NODE_WITH_SAME_SYSPROP:
if (sysPropsCache == null) {
throw new IllegalArgumentException("Unable to get the NodesSysPropsCacher on sorting replicas by preference:"+ preferenceRule.value);
}
lhs = hasSameMetric(left, preferenceRule.value);
rhs = hasSameMetric(right, preferenceRule.value);
break;
case ShardParams.SHARDS_PREFERENCE_REPLICA_BASE:
throw new IllegalArgumentException("only one base replica order may be specified in "
+ ShardParams.SHARDS_PREFERENCE + ", and it must be specified last");
default:
throw new IllegalArgumentException("Invalid " + ShardParams.SHARDS_PREFERENCE + " type: " + preferenceRule.name);
}
if (lhs != rhs) {
return lhs ? -1 : +1;
}
}
}
return 0;
}
private boolean hasSameMetric(Object o, String metricTag) {
if (!(o instanceof Replica)) {
return false;
}
Collection<String> tags = Collections.singletonList(metricTag);
String otherNodeName = ((Replica) o).getNodeName();
Map<String, Object> currentNodeMetric = sysPropsCache.getSysProps(nodeName, tags);
Map<String, Object> otherNodeMetric = sysPropsCache.getSysProps(otherNodeName, tags);
return currentNodeMetric.equals(otherNodeMetric);
}
private boolean hasCoreUrlPrefix(Object o, String prefix) {
final String s;
if (o instanceof String) {
s = (String)o;
}
else if (o instanceof Replica) {
s = ((Replica)o).getCoreUrl();
} else {
return false;
}
if (prefix.equals(ShardParams.REPLICA_LOCAL)) {
return !StringUtils.isEmpty(localHostAddress) && s.startsWith(localHostAddress);
} else {
return s.startsWith(prefix);
}
}
private static boolean hasReplicaType(Object o, String preferred) {
if (!(o instanceof Replica)) {
return false;
}
final String s = ((Replica)o).getType().toString();
return s.equalsIgnoreCase(preferred);
}
private static boolean hasLeaderStatus(Object o, String status) {
if (!(o instanceof Replica)) {
return false;
}
final boolean leaderStatus = ((Replica) o).isLeader();
return leaderStatus == Boolean.parseBoolean(status);
}
public List<PreferenceRule> getSortRules() {
return sortRules;
}
public List<PreferenceRule> getPreferenceRules() {
return preferenceRules;
}
}