/*
* Copyright 2014-2015 the original author or authors
*
* 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.wplatform.ddal.shards;
import java.util.Collection;
import java.util.Random;
import java.util.SortedMap;
import java.util.TreeMap;
import com.wplatform.ddal.util.MurmurHash;
/**
* @author <a href="mailto:jorgie.mail@gmail.com">jorgie li</a>
*
*/
public class ConsistentHashing implements LoadBalancingStrategy {
private static final int defaultNumberOfReplicas = 16;
private int numberOfReplicas = defaultNumberOfReplicas;
private Random random = new Random();
private boolean readOnly;
private SortedMap<Integer, DataSourceMarker> circle = new TreeMap<Integer, DataSourceMarker>();
public ConsistentHashing(Collection<DataSourceMarker> nodes, boolean readOnly) {
if (numberOfReplicas < 1) {
throw new IllegalArgumentException("The numberOfReplicas must bigger then 0.");
}
if (nodes == null || nodes.isEmpty()) {
throw new IllegalArgumentException("The shards can't empty.");
}
this.readOnly = readOnly;
for (DataSourceMarker node : nodes) {
add(node);
}
}
private static int hash(final String k) {
return MurmurHash.hash32(k);
}
private static String decorateWithCounter(final String input, final int counter) {
return new StringBuilder(input).append('%').append(counter).append('%').toString();
}
public void add(DataSourceMarker node) {
int weight = readOnly ? node.getrWeight() : node.getwWeight();
int nodeCount = numberOfReplicas * weight;
for (int i = 0; i < nodeCount; i++) {
String decorateWithCounter = decorateWithCounter(node.toString(), i);
circle.put(hash(decorateWithCounter), node);
}
}
public void remove(String node) {
for (int i = 0; i < numberOfReplicas; i++) {
circle.remove(hash(node.toString() + i));
}
}
public DataSourceMarker get(Object key) {
if (circle.isEmpty()) {
return null;
}
int hash = hash(key.toString());
if (!circle.containsKey(hash)) {
SortedMap<Integer, DataSourceMarker> tailMap = circle.tailMap(hash);
hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
}
return circle.get(hash);
}
public boolean hasNodes() {
return circle.isEmpty();
}
@Override
public DataSourceMarker next() {
return get(random.nextInt());
}
}