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

io.smallrye.stork.loadbalancer.leastresponsetime.LeastResponseTimeLoadBalancer Maven / Gradle / Ivy

package io.smallrye.stork.loadbalancer.leastresponsetime;

import java.util.Collection;

import io.smallrye.stork.api.LoadBalancer;
import io.smallrye.stork.api.NoServiceInstanceFoundException;
import io.smallrye.stork.api.ServiceInstance;
import io.smallrye.stork.impl.ServiceInstanceWithStatGathering;

public class LeastResponseTimeLoadBalancer implements LoadBalancer {

    // TODO sampling instead of collecting everything

    private final CallStatistics callStatistics = new CallStatistics();
    private final long retryAfterFailureTreshold;
    private final long forceRetryThreshold;

    public LeastResponseTimeLoadBalancer(LeastResponseTimeLoadBalancerProviderConfiguration config) {
        this.retryAfterFailureTreshold = Long.parseLong(config.getRetryAfterFailureThreshold());
        this.forceRetryThreshold = Long.parseLong(config.getForceRetryThreshold());
    }

    @Override
    public ServiceInstance selectServiceInstance(Collection serviceInstances) {
        if (serviceInstances.isEmpty()) {
            throw new NoServiceInstanceFoundException("No service instance found");
        }
        // we may want sampling in the future. Right now let's collect all the results.
        // compared to IO ops, it should be cheap...
        ServiceInstance best = null;
        CallStatistics.CallsData bestData = null;
        for (ServiceInstance instance : serviceInstances) {
            CallStatistics.CallsData callsData = callStatistics.statsForInstance(instance.getId());
            if (callsData == null) {
                callsData = callStatistics.init(instance.getId()); // to mark that it was used
                callsData.forcedAttemptInProgress.set(true);
                best = instance;
                break;
            } else if (bestData == null) {
                bestData = callsData;
                best = instance;
            } else {
                if (timeToRetry(callsData)) {
                    best = instance;
                    break;
                }
                if (isBetterThan(callsData, bestData)) {
                    best = instance;
                    bestData = callsData;
                }
            }
        }
        return new ServiceInstanceWithStatGathering(best, callStatistics);
    }

    private boolean isBetterThan(CallStatistics.CallsData callsData, CallStatistics.CallsData bestData) {
        if (bestData.lastSuccess != 0) {
            if (callsData.lastSuccess == 0) {
                return false;
            }
            long now = callStatistics.currentCall();
            // TODO we only take into account the best, we should probably use one of the bests
            // @formatter:off
            return callsData.scaledTime() * (1 + callsData.scaledErrorCount(now - callsData.lastFailure))
                    <= bestData.scaledTime() * (1 + bestData.scaledErrorCount(now - bestData.lastFailure));
            // @formatter:on
        } else if (callsData.lastSuccess != 0) {
            return true;
        } else {
            return callsData.lastFailure == 0 || callsData.lastFailure < bestData.lastFailure;
        }
    }

    private boolean timeToRetry(CallStatistics.CallsData callsData) {
        if (callsData == null) {
            return true;
        }
        long now = callStatistics.currentCall();
        return now - callsData.lastFailure > retryAfterFailureTreshold
                && now - callsData.lastSuccess > forceRetryThreshold
                && callsData.forcedAttemptInProgress.compareAndSet(false, true);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy