All Downloads are FREE. Search and download functionalities are using the official Maven repository.

netflix.ocelli.loadbalancer.DefaultLoadBalancer Maven / Gradle / Ivy

There is a newer version: 0.1.0-rc.2
Show newest version
package netflix.ocelli.loadbalancer;

import netflix.ocelli.ClientConnector;
import netflix.ocelli.FailureDetectorFactory;
import netflix.ocelli.ManagedLoadBalancer;
import netflix.ocelli.MembershipEvent;
import netflix.ocelli.MembershipEvent.EventType;
import netflix.ocelli.WeightingStrategy;
import netflix.ocelli.selectors.ClientsAndWeights;
import netflix.ocelli.util.RandomBlockingQueue;
import netflix.ocelli.util.RxUtil;
import netflix.ocelli.util.StateMachine;
import netflix.ocelli.util.StateMachine.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Subscriber;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.subscriptions.CompositeSubscription;
import rx.subscriptions.SerialSubscription;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * The ClientSelector keeps track of all existing hosts and returns a single host for each
 * call to acquire().
 * 
 * @author elandau
 *
 * @param 
 *
 */
public class DefaultLoadBalancer implements ManagedLoadBalancer {
    
    private static final Logger LOG = LoggerFactory.getLogger(DefaultLoadBalancer.class);
    
    private final Observable> hostSource;
    private final WeightingStrategy weightingStrategy;
    private final FailureDetectorFactory failureDetector;
    private final ClientConnector clientConnector;
    private final Func1 connectedHostCountStrategy;
    private final Func1 quaratineDelayStrategy;
    private final Func1, Observable> selectionStrategy;
    
    private final String name;

    /**
     * Composite subscription to keep track of all Subscriptions to be unsubscribed at
     * shutdown
     */
    private final CompositeSubscription cs = new CompositeSubscription();
    
    /**
     * Map of ALL existing hosts, connected or not
     */
    private final ConcurrentMap clients = new ConcurrentHashMap();
    
    /**
     * Queue of idle hosts that are not initialized to receive traffic
     */
    private final RandomBlockingQueue idleClients = new RandomBlockingQueue();
    
    /**
     * Map of ALL currently acquired clients.  This map contains both connected as well as connecting hosts.
     */
    private final Set acquiredClients = new HashSet();
    
    /**
     * Array of active and healthy clients that can receive traffic
     */
    private final CopyOnWriteArrayList activeClients = new CopyOnWriteArrayList();
    
    private State IDLE         = State.create("IDLE");
    private State CONNECTING   = State.create("CONNECTING");
    private State CONNECTED    = State.create("CONNECTED");
    private State QUARANTINED  = State.create("QUARANTINED");
    private State REMOVED      = State.create("REMOVED");

    public DefaultLoadBalancer(
            String name, 
            Observable> hostSource, 
            ClientConnector clientConnector, 
            FailureDetectorFactory failureDetector, 
            Func1, Observable> selectionStrategy, 
            Func1 quaratineDelayStrategy, 
            Func1 connectedHostCountStrategy, 
            WeightingStrategy weightingStrategy) {
        this.weightingStrategy          = weightingStrategy;
        this.connectedHostCountStrategy = connectedHostCountStrategy;
        this.quaratineDelayStrategy     = quaratineDelayStrategy;
        this.selectionStrategy          = selectionStrategy;
        this.name                       = name;
        this.failureDetector            = failureDetector;
        this.clientConnector            = clientConnector;
        this.hostSource                 = hostSource;
        
        initialize();
    }

    private void initialize() {
        
        IDLE
            .onEnter(new Func1>() {
                @Override
                public Observable call(Holder holder) {
                    LOG.info("{} - {} is idle", name, holder.getClient());
                    
                    idleClients.add(holder);
    
                    // Determine if a new host should be created based on the configured strategy
                    int idealCount = connectedHostCountStrategy.call(clients.size());
                    if (idealCount > acquiredClients.size()) {
                        acquireNextIdleHost()  
                            .first()
                            .subscribe(new Action1() {
                                @Override
                                public void call(Holder holder) {
                                    holder.sm.call(EventType.CONNECT);
                                }
                            });
                    }
                    return Observable.empty();
                }
            })
            .transition(EventType.CONNECT, CONNECTING)
            .transition(EventType.FAILED, QUARANTINED)
            .transition(EventType.CONNECTED, CONNECTED)
            ;
        
        CONNECTING
            .onEnter(new Func1>() {
                @Override
                public Observable call(final Holder holder) {
                    LOG.info("{} - {} is connecting", name, holder.getClient());
                    acquiredClients.add(holder);
                    holder.connect();
                    return Observable.empty();
                }
            })
            .transition(EventType.CONNECTED, CONNECTED)
            .transition(EventType.FAILED, QUARANTINED)
            .transition(EventType.REMOVE, REMOVED)
            ;
        
        CONNECTED
            .onEnter(new Func1>() {
                @Override
                public Observable call(Holder holder) {
                    LOG.info("{} - {} is connected", name, holder.getClient());
                    activeClients.add(holder.client);
                    return Observable.empty();
                }
            })
            .onExit(new Func1>() {
                @Override
                public Observable call(Holder holder) {
                    activeClients.remove(holder.client);
                    return Observable.empty();
                }
            })
            .ignore(EventType.CONNECTED)
            .ignore(EventType.CONNECT)
            .transition(EventType.FAILED, QUARANTINED)
            .transition(EventType.REMOVE, REMOVED)
            .transition(EventType.STOP, IDLE)
            ;
        
        QUARANTINED
            .onEnter(new Func1>() {
                @Override
                public Observable call(final Holder holder) {
                    LOG.info("{} - {} is quaratined ({})", name, holder.getClient(), holder.quaratineCounter);
                    acquiredClients.remove(holder);
                    
                    return Observable
                            .just(EventType.UNQUARANTINE)
                            .delay(quaratineDelayStrategy.call(holder.getQuaratineCounter()), TimeUnit.MILLISECONDS)
                            .doOnNext(RxUtil.info("Next:")); 
                }
            })
            .ignore(EventType.FAILED)
            .transition(EventType.UNQUARANTINE, IDLE)
            .transition(EventType.REMOVE, REMOVED)
            .transition(EventType.CONNECTED, CONNECTED)
            ;
        
        REMOVED
            .onEnter(new Func1>() {
                @Override
                public Observable call(Holder holder) {
                    LOG.info("{} - {} is removed", name, holder.getClient());
                    activeClients.remove(holder.getClient());
                    acquiredClients.remove(holder);
                    idleClients.remove(holder);
                    clients.remove(holder.client);
                    cs.remove(holder.cs);
                    return Observable.empty();
                }
        })
        ;
        cs.add(hostSource
            .subscribe(new Action1>() {
                @Override
                public void call(MembershipEvent event) {
                    Holder holder = clients.get(event.getClient());
                    if (holder == null) {
                        if (event.getType().equals(EventType.ADD)) {
                            final Holder newHolder = new Holder(event.getClient(), IDLE);
                            if (null == clients.putIfAbsent(event.getClient(), newHolder)) {
                                LOG.trace("{} - {} is added", name, newHolder.getClient());
                                newHolder.initialize();
                            }
                        }
                    }
                    else {
                        holder.sm.call(event.getType());
                    }
                }
            }));
    }
    
    /**
     * Holder the client state within the context of this LoadBalancer
     */
    public class Holder {
        final AtomicInteger quaratineCounter = new AtomicInteger();
        final C client;
        final StateMachine sm;
        final CompositeSubscription cs = new CompositeSubscription();
        final SerialSubscription connectSubscription = new SerialSubscription();
        
        public Holder(C client, State initial) {
            this.client = client;
            this.sm = StateMachine.create(this, initial);
        }
        
        public void initialize() {
            this.cs.add(sm.start().subscribe());
            this.cs.add(connectSubscription);
            this.cs.add(failureDetector.call(client).subscribe(new Action1() {
                @Override
                public void call(Throwable t1) {
                    sm.call(EventType.FAILED);
                    quaratineCounter.incrementAndGet();
                }
            }));
        }
        
        public void connect() {
            connectSubscription.set(clientConnector.call(client).subscribe(
                new Action1() {
                    @Override
                    public void call(C client) {
                        sm.call(EventType.CONNECTED);
                        quaratineCounter.set(0);
                    }
                },
                new Action1() {
                    @Override
                    public void call(Throwable t1) {
                        sm.call(EventType.FAILED);
                        quaratineCounter.incrementAndGet();
                    }
                }));
        }
        
        public int getQuaratineCounter() {
            return quaratineCounter.get();
        }

        public void shutdown() {
            cs.unsubscribe();
        }
        
        public String toString() {
            return "Holder[" + name + "-" + client + "]";
        }

        public C getClient() {
            return client;
        }
    }
    
    public void shutdown() {
        cs.unsubscribe();
    }
    
    /**
     * @return Return the next idle host or empty() if none available
     */
    private Observable acquireNextIdleHost() {
        return Observable.create(new OnSubscribe() {
            @Override
            public void call(Subscriber s) {
                try {
                    Holder holder = idleClients.poll();
                    if (holder != null)
                        s.onNext(holder);
                    s.onCompleted();
                }
                catch (Exception e) {
                    s.onError(e);
                }
            }
        });
    }
    
    /**
     * Acquire the most recent list of hosts
     */
    @Override
    public Observable choose() {
        if (activeClients.isEmpty()) {
            return Observable.error(new NoSuchElementException("No servers available in the load balancer: " + name));
        }
        return selectionStrategy.call(
                weightingStrategy.call(new ArrayList(activeClients)));
    }

    @Override
    public Observable listAllClients() {
        return Observable.from(new HashSet(clients.keySet()));
    }
    
    @Override
    public Observable listActiveClients() {
        return Observable.from(activeClients);
    }
    
    public String getName() {
        return name;
    }
    
    public String toString() {
        return "DefaultLoadBalancer[" + name + "]";
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy