netflix.ocelli.topologies.RingTopology Maven / Gradle / Ivy
package netflix.ocelli.topologies;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import netflix.ocelli.Instance;
import netflix.ocelli.MutableInstance;
import rx.Observable;
import rx.Observable.Transformer;
import rx.functions.Action0;
import rx.functions.Func0;
import rx.functions.Func1;
/**
* The ring topology uses consistent hashing to arrange all hosts in a predictable ring
* topology such that each client instance will be located in a uniformly distributed
* fashion around the ring. The client will then target the next N hosts after it's location.
*
* This type of topology ensures that each client instance communicates with a subset of
* hosts in such a manner that the overall load shall be evenly distributed.
*
* @author elandau
*
* @param
* @param
*/
public class RingTopology, T> implements Transformer, Instance> {
private final Instance localMember;
private final Func1 countFunc;
private final Comparator> comparator;
private final Func1 keyFunc;
public RingTopology(final K localKey, final Func1 keyFunc, Func1 countFunc) {
this.localMember = MutableInstance.from((T)null);
this.countFunc = countFunc;
this.keyFunc = keyFunc;
this.comparator = new Comparator>() {
@Override
public int compare(Instance o1, Instance o2) {
K k1 = o1.getValue() == null ? localKey : keyFunc.call(o1.getValue());
K k2 = o2.getValue() == null ? localKey : keyFunc.call(o2.getValue());
return k1.compareTo(k2);
}
};
}
@Override
public Observable> call(Observable> o) {
return o.flatMap(new Func1, Observable>>() {
final List> ring = new ArrayList>();
Map> members = new HashMap>();
{
ring.add(localMember);
}
@Override
public Observable> call(final Instance member) {
ring.add(member);
return update().concatWith(member.flatMap(
new Func1>>() {
@Override
public Observable> call(Boolean t) {
return Observable.empty();
}
},
new Func1>>() {
@Override
public Observable> call(Throwable t1) {
ring.remove(t1);
return update();
}
},
new Func0>>() {
@Override
public Observable> call() {
ring.remove(member);
return update();
}
}));
}
private Observable> update() {
Collections.sort(ring, comparator);
// -1 to account for the current instance
int count = Math.min(ring.size() - 1, countFunc.call(ring.size() - 1));
List> toAdd = new ArrayList>();
List> toRemove = new ArrayList>();
int pos = Collections.binarySearch(ring, localMember, comparator) + 1;
Map> newMembers = new HashMap>();
for (int i = 0; i < count; i++) {
Instance member = ring.get((pos + i) % ring.size());
MutableInstance existing = members.remove(keyFunc.call(member.getValue()));
if (existing == null) {
MutableInstance newMember = MutableInstance.from(member.getValue());
newMembers.put(keyFunc.call(member.getValue()), newMember);
toAdd.add(newMember);
}
else {
newMembers.put(keyFunc.call(member.getValue()), existing);
}
}
for (MutableInstance member : members.values()) {
toRemove.add(member);
}
members = newMembers;
return response(toAdd, toRemove);
}
private Observable> response(List> toAdd, final List> toRemove) {
return Observable.from(toAdd).doOnCompleted(new Action0() {
@Override
public void call() {
for (MutableInstance member : toRemove) {
member.close();
}
}
});
}
});
}
}