/**
* 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 com.datastax.drivers.jdbc.pool.cassandra.connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.datastax.drivers.jdbc.pool.cassandra.utils.DaemonThreadPoolFactory;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
/**
* This LB Algorithm has the Phi algo which Dynamic snitch uses, LB is based on the probablity of failure of the node.
* TODO: Make cassandra code abstracted enough so we can inherit from the same.
*
* @author Vijay Parthasarathy
*/
public class DynamicLoadBalancingPolicy implements LoadBalancingPolicy {
private static final long serialVersionUID = -1044985880174118325L;
private static final Logger log = LoggerFactory.getLogger(DynamicLoadBalancingPolicy.class);
private final ScheduledExecutorService tasks = new ScheduledThreadPoolExecutor(1, new DaemonThreadPoolFactory(getClass()));
// references which is used to make the real time requests faster.
private Map<HClientPool, Double> scores = Maps.newConcurrentMap();
private List<LatencyAwareHClientPool> allPools = new CopyOnWriteArrayList<LatencyAwareHClientPool>();
// default values this can be changed by the Client.
private int UPDATE_INTERVAL = 100;
private int RESET_INTERVAL = 20000;
private double DYNAMIC_BADNESS_THRESHOLD = 0.10;
public DynamicLoadBalancingPolicy() {
// Pre-calculate the scores so as we can compare it fast.
Runnable updateThread = new Runnable() {
public void run() {
try {
updateScores();
} catch(Exception e) {
log.info("exception updating scores", e);
}
}
};
// Clear Stats.
Runnable resetThread = new Runnable() {
public void run() {
try {
for (LatencyAwareHClientPool pool : allPools) {
pool.clear();
}
} catch(Exception e) {
log.info("exceotuib reseting stats", e);
}
}
};
tasks.scheduleWithFixedDelay(updateThread, UPDATE_INTERVAL, UPDATE_INTERVAL, TimeUnit.MILLISECONDS);
tasks.scheduleWithFixedDelay(resetThread, RESET_INTERVAL, RESET_INTERVAL, TimeUnit.MILLISECONDS);
}
@Override
public HClientPool getPool(Collection<HClientPool> pools, Set<CassandraHost> excludeHosts) {
List<HClientPool> poolList = Lists.newArrayList(pools);
// remove the hosts from the list.
if (excludeHosts != null) {
filter(poolList, excludeHosts);
}
Collections.shuffle(poolList);
HClientPool fp = poolList.get(0);
Double first = scores.get(fp);
for (int i = 1; i < poolList.size(); i++) {
HClientPool np = poolList.get(i);
Double next = scores.get(np);
if ((first - next) / first > DYNAMIC_BADNESS_THRESHOLD) {
Collections.sort(poolList, new SortByScoreComparator());
if (log.isDebugEnabled())
log.debug(String.format("According to score we have chosen {} vs first {}", poolList.get(0), fp));
break;
}
}
return poolList.get(0);
}
private void filter(List<HClientPool> from, Set<CassandraHost> subList) {
Iterator<HClientPool> it = from.iterator();
while (it.hasNext()) {
if (subList.contains(it.next().getCassandraHost()))
it.remove();
}
}
private class SortByScoreComparator implements Comparator<HClientPool> {
public int compare(HClientPool p1, HClientPool p2) {
Double scored1 = scores.get(p1);
Double scored2 = scores.get(p2);
if (scored1.equals(scored2))
return 0;
if (scored1 < scored2)
return -1;
else return 1;
}
}
@Override
public HClientPool createConnection(CassandraHost host) throws SQLException {
LatencyAwareHClientPool pool = new LatencyAwareHClientPool(host);
add(pool);
return pool;
}
// This is helper class for the test cases. TODO: cleanup.
void add(LatencyAwareHClientPool pool) {
allPools.add(pool);
// update the reference of the scores intially it is Zero.
scores.put(pool, 0.0);
}
// This will be a expensive call.
void updateScores() {
for (LatencyAwareHClientPool pool : allPools) {
scores.put(pool, pool.score());
pool.resetIntervel();
}
}
public int getUpdateInterval() {
return UPDATE_INTERVAL;
}
/**
* Set the configured interval for the stats to be recalculated (until this time it is been cached.
*
* @param updateInterval
* In ms.
*/
public void setUpdateInterval(int updateInterval) {
UPDATE_INTERVAL = updateInterval;
}
public int getResetInterval() {
return RESET_INTERVAL;
}
/**
* Set the configured interval for the stats to be reset so that the new stats are allowed and we can get rid of bad
* nodes value. This is under the assumption that the bad nodes will eventually get better....
*
* @param resetInterval
* in ms
*/
public void setResetInterval(int resetInterval) {
RESET_INTERVAL = resetInterval;
}
public double getBadnessThreshold() {
return DYNAMIC_BADNESS_THRESHOLD;
}
/**
* This is the percentage of badness which is acceptable...
*
* Example: A should be 0.20 (20%) bad than B before B is choosen rathar than A.
*
* @param badness
* in %
*/
public void setBadnessThreshold(double badness) {
DYNAMIC_BADNESS_THRESHOLD = badness;
}
}