package netflix.ocelli; import rx.Observable; import rx.Observable.Transformer; import rx.functions.Func1; import rx.observables.GroupedObservable; public class KeyedInstance<K, T> { private final K key; private final Instance<T> member; KeyedInstance(K key, Instance<T> member) { this.key = key; this.member = member; } /** * Partition Members into multiple partitions based on a partition function. * It's possible for a member to exist in multiple partitions. Each partition * is a GroupedObservable of members for that partition alone. This stream can * be fed into a load balancer. * * Partitions are useful in the following use cases. * * 1. Hosts are grouped into VIPs where each VIP subset can service a certain subset of * requests. In the example below API's provided by vip1 can be serviced by Hosts 1,2,3 * whereas API's provied by vip2 can only be serviced by hosts 2 and 3. * * VIP : F(Host) -> O(vip) Multiple vips * * <vip1> Host1, Host2, Host3 * <vip2> Host2, Host3 * * 2. Shard or hash aware clients using consistent hashing (ex. Cassandra) or sharding (ex. EvCache) * will opt to send traffic only to nodes that can own the data. The partitioner function * will return the tokenRangeId or shardId for each host. Note that for replication factor of 1 * each shard will contain only 1 host while for higher replication factors each shard will contain * multiple hosts (equal to the number of shards) and that these hosts will overlap. * * <range1> Host1, Host2 * <range2> Host2, Host3 * <range3> Host3, Host4 * <range4> Host4, Host5 * * @author elandau * * @param <C> Client type * @param <K> The partition key */ public static <K, T> Transformer<Instance<T>, GroupedObservable<K, Instance<T>>> partitionBy(final Func1<T, Observable<K>> partitioner) { return new Transformer<Instance<T>, GroupedObservable<K, Instance<T>>>() { @Override public Observable<GroupedObservable<K, Instance<T>>> call(final Observable<Instance<T>> o) { return o .flatMap(new Func1<Instance<T>, Observable<KeyedInstance<K, T>>>() { @Override public Observable<KeyedInstance<K, T>> call(final Instance<T> member) { return partitioner .call(member.getValue()) .map(new Func1<K, KeyedInstance<K, T>>() { @Override public KeyedInstance<K, T> call(K key) { return new KeyedInstance<K, T>(key, member); } }); } }) .groupBy( new Func1<KeyedInstance<K, T>, K>() { @Override public K call(KeyedInstance<K, T> t1) { return t1.key; } }, new Func1<KeyedInstance<K, T>, Instance<T>>() { @Override public Instance<T> call(KeyedInstance<K, T> t1) { return t1.member; } }); } }; } }