/**
* Copyright 2016 Yahoo Inc.
*
* 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 com.yahoo.pulsar.broker.loadbalance.impl;
import java.util.ArrayList;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.yahoo.pulsar.broker.BrokerData;
import com.yahoo.pulsar.broker.BundleData;
import com.yahoo.pulsar.broker.LocalBrokerData;
import com.yahoo.pulsar.broker.ServiceConfiguration;
import com.yahoo.pulsar.broker.TimeAverageBrokerData;
import com.yahoo.pulsar.broker.TimeAverageMessageData;
import com.yahoo.pulsar.broker.loadbalance.LoadData;
import com.yahoo.pulsar.broker.loadbalance.ModularLoadManagerStrategy;
/**
* Placement strategy which selects a broker based on which one has the least long term message rate.
*/
public class LeastLongTermMessageRate implements ModularLoadManagerStrategy {
private static Logger log = LoggerFactory.getLogger(LeastLongTermMessageRate.class);
// Maintain this list to reduce object creation.
private ArrayList<String> bestBrokers;
public LeastLongTermMessageRate(final ServiceConfiguration conf) {
bestBrokers = new ArrayList<>();
}
// Form a score for a broker using its preallocated bundle data and time average data.
// This is done by summing all preallocated long-term message rates and adding them to the broker's overall
// long-term message rate, which is itself the sum of the long-term message rate of every allocated bundle.
// Once the total long-term message rate is calculated, the score is then weighted by
// max_usage < overload_threshold ? 1 / (overload_threshold - max_usage): Inf
// This weight attempts to discourage the placement of bundles on brokers whose system resource usage is high.
private static double getScore(final BrokerData brokerData, final ServiceConfiguration conf) {
final double overloadThreshold = conf.getLoadBalancerBrokerOverloadedThresholdPercentage() / 100.0;
double totalMessageRate = 0;
for (BundleData bundleData : brokerData.getPreallocatedBundleData().values()) {
final TimeAverageMessageData longTermData = bundleData.getLongTermData();
totalMessageRate += longTermData.getMsgRateIn() + longTermData.getMsgRateOut();
}
final TimeAverageBrokerData timeAverageData = brokerData.getTimeAverageData();
final double maxUsage = brokerData.getLocalData().getMaxResourceUsage();
if (maxUsage > overloadThreshold) {
return Double.POSITIVE_INFINITY;
}
// 1 / weight is the proportion of load this machine should receive in proportion to a machine with no system
// resource burden. This attempts to spread out the load in such a way that machines only become overloaded if
// there is too much load for the system to handle (e.g., all machines are at least nearly overloaded).
final double weight = 1 / (overloadThreshold - maxUsage);
final double totalMessageRateEstimate = totalMessageRate + timeAverageData.getLongTermMsgRateIn()
+ timeAverageData.getLongTermMsgRateOut();
return weight * totalMessageRateEstimate;
}
/**
* Find a suitable broker to assign the given bundle to.
*
* @param candidates
* The candidates for which the bundle may be assigned.
* @param bundleToAssign
* The data for the bundle to assign.
* @param loadData
* The load data from the leader broker.
* @param conf
* The service configuration.
* @return The name of the selected broker as it appears on ZooKeeper.
*/
@Override
public String selectBroker(final Set<String> candidates, final BundleData bundleToAssign, final LoadData loadData,
final ServiceConfiguration conf) {
bestBrokers.clear();
double minScore = Double.POSITIVE_INFINITY;
// Maintain of list of all the best scoring brokers and then randomly
// select one of them at the end.
for (String broker : candidates) {
final BrokerData brokerData = loadData.getBrokerData().get(broker);
final double score = getScore(brokerData, conf);
if (score == Double.POSITIVE_INFINITY) {
final LocalBrokerData localData = brokerData.getLocalData();
log.warn(
"Broker {} is overloaded: CPU: {}%, MEMORY: {}%, DIRECT MEMORY: {}%, BANDWIDTH IN: {}%, "
+ "BANDWIDTH OUT: {}%",
broker, localData.getCpu().percentUsage(), localData.getMemory().percentUsage(),
localData.getDirectMemory().percentUsage(), localData.getBandwidthIn().percentUsage(),
localData.getBandwidthOut().percentUsage());
}
log.debug("{} got score {}", broker, score);
if (score < minScore) {
// Clear best brokers since this score beats the other brokers.
bestBrokers.clear();
bestBrokers.add(broker);
minScore = score;
} else if (score == minScore) {
// Add this broker to best brokers since it ties with the best score.
bestBrokers.add(broker);
}
}
if (bestBrokers.isEmpty()) {
// All brokers are overloaded.
// Assign randomly in this case.
bestBrokers.addAll(candidates);
}
return bestBrokers.get(ThreadLocalRandom.current().nextInt(bestBrokers.size()));
}
}