Ribbon's Best Available Rule and Retry Rule

Keywords: Programming Attribute github

Ribbon's version is 2.3.0.release.

1.BestAvailableRule

                    

Figure 1

ClientConfigEnabled Round Robin Rule defines a class attribute, Round Robin Rule, which is selected by calling Round Robin Rule in the selection method, so here is the rotation algorithm.

    List-1.1

public class ClientConfigEnabledRoundRobinRule extends AbstractLoadBalancerRule {
    RoundRobinRule roundRobinRule = new RoundRobinRule();

    public ClientConfigEnabledRoundRobinRule() {
    }

    public void initWithNiwsConfig(IClientConfig clientConfig) {
        this.roundRobinRule = new RoundRobinRule();
    }

    public void setLoadBalancer(ILoadBalancer lb) {
        super.setLoadBalancer(lb);
        this.roundRobinRule.setLoadBalancer(lb);
    }

    public Server choose(Object key) {
        if (this.roundRobinRule != null) {
            return this.roundRobinRule.choose(key);
        } else {
            throw new IllegalArgumentException("This class has not been initialized with the RoundRobinRule class");
        }
    }
}

Best Available Rule inherits Client Config Enabled Round Robin Rule and implements List-1.2 internally to traverse all service providers and select the service with the smallest concurrency.

    List-1.2

private LoadBalancerStats loadBalancerStats;

public Server choose(Object key) {
    if (this.loadBalancerStats == null) {
        return super.choose(key);
    } else {
        List<Server> serverList = this.getLoadBalancer().getAllServers();
        int minimalConcurrentConnections = 2147483647;
        long currentTime = System.currentTimeMillis();
        Server chosen = null;
        Iterator var7 = serverList.iterator();

        while(var7.hasNext()) {
            Server server = (Server)var7.next();
            ServerStats serverStats = this.loadBalancerStats.getSingleServerStat(server);
            if (!serverStats.isCircuitBreakerTripped(currentTime)) {
                int concurrentConnections = serverStats.getActiveRequestsCount(currentTime);
                if (concurrentConnections < minimalConcurrentConnections) {
                    minimalConcurrentConnections = concurrentConnections;
                    chosen = server;
                }
            }
        }

        if (chosen == null) {
            return super.choose(key);
        } else {
            return chosen;
        }
    }
}

public void setLoadBalancer(ILoadBalancer lb) {
    super.setLoadBalancer(lb);
    if (lb instanceof AbstractLoadBalancer) {
        this.loadBalancerStats = ((AbstractLoadBalancer)lb).getLoadBalancerStats();
    }

}

The choose method restores the choose method in the parent class.

  1. Get the list of services and traverse the services
  2. Get the number of concurrent connections for the current service instance through ServerStats, as shown in List-3 below. If the number of concurrent connections is not zero and the current time is within the range of the last valid change time, the current number of concurrent connections is returned.
  3. After traversing all service providers, if the resulting server is null, the parent class's choose method is invoked and the RoundRobin algorithm is used for selection.

    List-1.3

public int getActiveRequestsCount(long currentTime) {
    int count = this.activeRequestsCount.get();
    if (count == 0) {
        return 0;
    } else if (currentTime - this.lastActiveRequestsCountChangeTimestamp <= (long)(activeRequestsCountTimeout.get() * 1000) && count >= 0) {
        return count;
    } else {
        this.activeRequestsCount.set(0);
        return 0;
    }
}

2.RetryRule

                                       

Figure 2

The implementation of RetryRule is relatively simple, based on Round Robin Rule, as shown in List-2.1 below.

    List-2.1

public class RetryRule extends AbstractLoadBalancerRule {
    IRule subRule = new RoundRobinRule();
    long maxRetryMillis = 500L;
    ...
    public Server choose(ILoadBalancer lb, Object key) {
        long requestTime = System.currentTimeMillis();
        long deadline = requestTime + this.maxRetryMillis;
        Server answer = null;
        answer = this.subRule.choose(key);
        if ((answer == null || !answer.isAlive()) && System.currentTimeMillis() < deadline) {
            InterruptTask task = new InterruptTask(deadline - System.currentTimeMillis());

            while(!Thread.interrupted()) {
                answer = this.subRule.choose(key);
                if (answer != null && answer.isAlive() || System.currentTimeMillis() >= deadline) {
                    break;
                }

                Thread.yield();
            }

            task.cancel();
        }

        return answer != null && answer.isAlive() ? answer : null;
    }
    ...

In RetryRule, select (Object key) calls select (ILoadBalancer lb, Object key).

  1. The current time plus maxRetryMillis gets deadline, which is the deadline
  2. Get the service server with subRule and return it directly if the service is valid
  3. Construct InterruptTask, which has a Timer timer task, such as List-2.2. Later, as long as the current thread is not interrupt ed, a service instance is selected using the Round Robin algorithm of subRule, and if the service is valid or the current time has passed the deadline, it will jump out of the cycle.
  4. If the service instance obtained in step 3 is invalid and the current time is within the deadline, Thread.yield() is called to allow thread resources to other threads.

The source code shows that RetryRule only retrieves the service instance with subRule again after subRule.choose obtains the invalid service instance, and will not keep trying, that is, try once.

    List-2.2

public class InterruptTask extends TimerTask {
    static Timer timer = new Timer("InterruptTimer", true);
    protected Thread target = null;

    public InterruptTask(long millis) {
        this.target = Thread.currentThread();
        timer.schedule(this, millis);
    }

    public boolean cancel() {
        try {
            return super.cancel();
        } catch (Exception var2) {
            return false;
        }
    }

    public void run() {
        if (this.target != null && this.target.isAlive()) {
            this.target.interrupt();
        }
    }
    ...

Reference

  1. https://github.com/Netflix/ribbon/tree/v2.3.0

Posted by Unforgiven on Mon, 07 Oct 2019 11:12:49 -0700