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

com.github.phantomthief.failover.impl.PartitionFailover Maven / Gradle / Ivy

package com.github.phantomthief.failover.impl;

import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;

import java.io.Closeable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.github.phantomthief.failover.Failover;

/**
 * @author huangli
 * Created on 2019-12-10
 */
public class PartitionFailover implements Failover, Closeable {

    private final WeightFailover weightFailover;
    private final long maxExternalPoolIdleMillis;
    private final int totalResourceSize;
    private volatile ResEntry[] resources;

    @SuppressWarnings("checkstyle:VisibilityModifier")
    private static class ResEntry {
        final T object;
        final int initWeight;

        volatile long lastReturnNanoTime;
        AtomicInteger concurrency;

        ResEntry(T object, int initWeight, int initConcurrency) {
            this.object = object;
            this.initWeight = initWeight;
            this.concurrency = new AtomicInteger(initConcurrency);
        }

    }

    @SuppressWarnings("checkstyle:VisibilityModifier")
    private static class ResEntryEx extends ResEntry {
        double scoreWeight;

        ResEntryEx(T object, int initWeight, int initConcurrency) {
            super(object, initWeight, initConcurrency);
        }
    }

    @SuppressWarnings("unchecked")
    PartitionFailover(PartitionFailoverBuilder partitionFailoverBuilder,
            WeightFailover weightFailover) {
        this.weightFailover = weightFailover;
        this.totalResourceSize = weightFailover.getAll().size();
        this.maxExternalPoolIdleMillis = partitionFailoverBuilder.maxExternalPoolIdleMillis;
        int corePartitionSize = partitionFailoverBuilder.corePartitionSize;
        List available = weightFailover.getAvailable(corePartitionSize);
        if (available.size() < corePartitionSize) {
            corePartitionSize = available.size();
        }
        this.resources = new ResEntry[corePartitionSize];
        for (int i = 0; i < corePartitionSize; i++) {
            T one = available.get(i);
            resources[i] = new ResEntry<>(one, weightFailover.initWeight(one), 0);
        }
    }

    public static  PartitionFailoverBuilder newBuilder() {
        return new PartitionFailoverBuilder<>();
    }

    private ResEntryEx[] deepCopyResource() {
        ResEntry[] refCopy = resources;
        @SuppressWarnings("unchecked")
        ResEntryEx[] copy = new ResEntryEx[refCopy.length];
        for (int i = 0; i < refCopy.length; i++) {
            ResEntry res = refCopy[i];
            ResEntryEx resEx = new ResEntryEx<>(res.object, res.initWeight, res.concurrency.get());
            resEx.lastReturnNanoTime = res.lastReturnNanoTime;
            copy[i] = resEx;
        }
        return copy;
    }

    @Override
    public List getAll() {
        return weightFailover.getAll();
    }

    @Override
    public void fail(@Nonnull T object) {
        weightFailover.fail(object);
        subtractConcurrency(object);
        if (weightFailover.currentWeight(object) <= 0) {
            replaceDownResource(object);
        }
    }

    @Override
    public void down(@Nonnull T object) {
        weightFailover.down(object);
        subtractConcurrency(object);
        replaceDownResource(object);
    }

    @Override
    public void success(@Nonnull T object) {
        weightFailover.success(object);
        subtractConcurrency(object);
    }

    @Nullable
    private ResEntry lookup(Object object) {
        ResEntry[] refCopy = resources;
        for (ResEntry res : refCopy) {
            if (res.object == object) {
                return res;
            }
        }
        return null;
    }

    private void subtractConcurrency(@Nonnull T object) {
        ResEntry resEntry = lookup(object);
        if (resEntry == null) {
            return;
        }
        resEntry.lastReturnNanoTime = System.nanoTime();
        resEntry.concurrency.updateAndGet(oldValue -> Math.max(oldValue - 1, 0));
    }

    private void addConcurrency(@Nonnull T object) {
        ResEntry resEntry = lookup(object);
        if (resEntry == null) {
            return;
        }
        resEntry.lastReturnNanoTime = System.nanoTime();
        resEntry.concurrency.updateAndGet(oldValue -> Math.max(oldValue + 1, 1));
    }

    private synchronized void replaceDownResource(T object) {
        ResEntry[] resourceRefCopy = resources;
        if (resourceRefCopy.length == totalResourceSize) {
            // so there is no more resource in weightFailover
            return;
        }
        @SuppressWarnings("unchecked")
        ResEntry[] newList = new ResEntry[resourceRefCopy.length];
        int index = -1;
        for (int i = 0; i < resourceRefCopy.length; i++) {
            newList[i] = resourceRefCopy[i];
            if (newList[i].object == object) {
                index = i;
            }
        }
        if (index == -1) {
            // maybe replaced by another thread
            return;
        }
        List excludes = Stream.of(resourceRefCopy).map(r -> r.object).collect(toList());
        T newOne = weightFailover.getOneAvailableExclude(excludes);
        if (newOne == null) {
            //no more available
            return;
        }
        newList[index] = new ResEntry<>(newOne, weightFailover.initWeight(newOne), 0);
        resources = newList;
    }


    @Nullable
    @Override
    public T getOneAvailable() {
        return getOneAvailableExclude(Collections.emptyList());
    }

    @Nullable
    @Override
    public T getOneAvailableExclude(Collection exclusions) {
        // we use recent resource when:
        // 1, tps of caller is slow (all concurrency is 0)
        boolean noCallInProgress = true;
        // 2, all resources are healthy (all currentWeight == initWeight)
        boolean allResIsHealthy = true;
        // 3, at least there is one call returned in recent
        boolean hasRecentReturnedCall = false;
        final long nowNanoTime = System.nanoTime();
        final ResEntryEx[] resourcesCopy = deepCopyResource();

        double sumOfScoreWeight = 0;
        int recentestIndex = 0;
        long maxTime = 0;

        for (int i = 0; i < resourcesCopy.length; i++) {
            ResEntryEx res = resourcesCopy[i];
            int currentWeight = weightFailover.currentWeight(res.object);
            res.scoreWeight = 1.0 * currentWeight / (res.concurrency.get() + 1);
            if (res.scoreWeight < 0) {
                // something wrong
                res.scoreWeight = 0;
            }
            if (!exclusions.contains(res.object)) {
                sumOfScoreWeight += res.scoreWeight;
                if (maxTime < res.lastReturnNanoTime) {
                    maxTime = res.lastReturnNanoTime;
                    recentestIndex = i;
                }
            }
            if (res.concurrency.get() > 0) {
                noCallInProgress = false;
            }
            if (currentWeight != res.initWeight) {
                allResIsHealthy = false;
            }
            long elapseMillis = (nowNanoTime - res.lastReturnNanoTime) / (1000 * 1000);
            if (elapseMillis >= 0 && elapseMillis < maxExternalPoolIdleMillis) {
                hasRecentReturnedCall = true;
            }
        }
        T one;
        if (maxExternalPoolIdleMillis > 0 && noCallInProgress && allResIsHealthy && hasRecentReturnedCall) {
            one = resourcesCopy[recentestIndex].object;
        } else {
            one = selectByScore(resourcesCopy, exclusions, sumOfScoreWeight);
        }
        if (one != null) {
            addConcurrency(one);
        }
        return one;
    }

    private static  T selectByScore(ResEntryEx[] resourcesCopy, Collection exclusions, double sumOfScoreWeight) {
        if (sumOfScoreWeight <= 0) {
            // all down
            return null;
        }
        double selectValue = ThreadLocalRandom.current().nextDouble(sumOfScoreWeight);
        double x = 0;
        for (ResEntryEx res : resourcesCopy) {
            if (exclusions.contains(res.object)) {
                continue;
            }
            x += res.scoreWeight;
            if (selectValue < x) {
                return res.object;
            }
        }
        // something wrong or there are float precision problem
        return resourcesCopy[0].object;
    }

    @Override
    public List getAvailable() {
        ResEntry[] resourceRefCopy = resources;
        if (resourceRefCopy.length == totalResourceSize) {
            return weightFailover.getAvailable();
        } else {
            return Stream.of(resourceRefCopy)
                    .filter(r -> weightFailover.currentWeight(r.object) > 0)
                    .map(r -> r.object)
                    .collect(collectingAndThen(toList(), Collections::unmodifiableList));
        }
    }

    @Override
    public Set getFailed() {
        return weightFailover.getFailed();
    }

    @Override
    public void close() {
        weightFailover.close();
    }

    @Override
    public List getAvailable(int n) {
        // we don't know which resource is used, so this method is not supported
        throw new UnsupportedOperationException();
    }

    @Override
    public List getAvailableExclude(Collection exclusions) {
        // we don't know which resource is used, so this method is not supported
        throw new UnsupportedOperationException();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy