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

com.netflix.concurrency.limits.strategy.LookupPartitionStrategy Maven / Gradle / Ivy

package com.netflix.concurrency.limits.strategy;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

import com.netflix.concurrency.limits.MetricIds;
import com.netflix.concurrency.limits.MetricRegistry;
import com.netflix.concurrency.limits.MetricRegistry.SampleListener;
import com.netflix.concurrency.limits.Strategy;
import com.netflix.concurrency.limits.internal.EmptyMetricRegistry;
import com.netflix.concurrency.limits.internal.Preconditions;

/**
 * Strategy for partitioning the limiter by named groups where the allocation of 
 * group to percentage is provided up front. 
 * @param 
 */
public class LookupPartitionStrategy implements Strategy {
    private static final String PARTITION_TAG_NAME = "partition";
    
    public static class Builder {
        private final Function lookup;
        private final Map partitions = new LinkedHashMap<>();
        private MetricRegistry registry = EmptyMetricRegistry.INSTANCE;
        
        protected Builder(Function lookup) {
            this.lookup = lookup;
        }

        public Builder metricRegistry(MetricRegistry registry) {
            this.registry = registry;
            return this;
        }
        
        public Builder assign(String group, Double percent) {
            partitions.put(group, new Partition(group, percent));
            return this;
        }
        
        public LookupPartitionStrategy build() {
            return new LookupPartitionStrategy(this);
        }

        public boolean hasPartitions() {
            return !partitions.isEmpty();
        }
    }
    
    public static  Builder newBuilder(Function lookup) {
        return new Builder(lookup);
    }

    private final Map partitions;
    private final Partition unknownPartition;
    private final Function lookup;
    private int busy = 0;
    private int limit = 0;
    
    private LookupPartitionStrategy(Builder builder) {
        Preconditions.checkArgument(!builder.partitions.isEmpty(), "No partitions specified");
        Preconditions.checkArgument(builder.partitions.values().stream().map(Partition::getPercent).reduce(0.0, Double::sum) <= 1.0, 
                "Sum of percentages must be <= 1.0");

        this.partitions = new HashMap<>(builder.partitions);
        this.partitions.forEach((name, partition) -> partition.createMetrics(builder.registry));
        
        this.unknownPartition = new Partition("unknown", 0.0);
        this.unknownPartition.createMetrics(builder.registry);
        
        this.lookup = builder.lookup;
        
        builder.registry.registerGauge(MetricIds.LIMIT_GUAGE_NAME, this::getLimit);
    }
    
    @Override
    public synchronized Token tryAcquire(T type) {
        final Partition partition = partitions.getOrDefault(lookup.apply(type), this.unknownPartition);
        
        if (busy >= limit && partition.isLimitExceeded()) {
            return Token.newNotAcquired(busy);
        }
        busy++;
        partition.acquire();
        return Token.newAcquired(busy, () -> releasePartition(partition));
    }

    private synchronized void releasePartition(Partition partition) {
        busy--;
        partition.release();
    }

    @Override
    public synchronized void setLimit(int newLimit) { 
        if (this.limit != newLimit) {
            this.limit = newLimit;
            partitions.forEach((name, partition) -> partition.updateLimit(newLimit));
        }
    }

    public synchronized int getLimit() {
        return limit;
    }

    private static class Partition {
        private final double percent;
        private final String name;
        private SampleListener busyDistribution;
        private int limit;
        private int busy;
        
        public Partition(String name, double pct) {
            this.name = name;
            this.percent = pct;
        }
        
        public void createMetrics(MetricRegistry registry) {
            this.busyDistribution = registry.registerDistribution(MetricIds.INFLIGHT_GUAGE_NAME, PARTITION_TAG_NAME, name);
            registry.registerGauge(MetricIds.PARTITION_LIMIT_GUAGE_NAME, this::getLimit, PARTITION_TAG_NAME, name);
        }
        
        public void updateLimit(int totalLimit) {
            // Calculate this bin's limit while rounding up and ensuring the value
            // is at least 1.  With this technique the sum of bin limits may end up being 
            // higher than the concurrency limit.
            this.limit = (int)Math.max(1, Math.ceil(totalLimit * percent));
        }

        public boolean isLimitExceeded() {
            return busy >= limit;
        }

        public void acquire() {
            busy++;
            busyDistribution.addSample(busy);
        }
        
        public void release() {
            busy--;
        }

        public int getLimit() {
            return limit;
        }
        
        public double getPercent() {
            return percent;
        }
        
        @Override
        public String toString() {
            return "Partition [pct=" + percent + ", limit=" + limit + ", busy=" + busy + "]";
        }
    }

    synchronized int getBinBusyCount(String key) {
        return Optional.ofNullable(partitions.get(key))
                .orElseThrow(() -> new IllegalArgumentException("Invalid group " + key))
                .busy;
    }

    synchronized int getBinLimit(String key) {
        return Optional.ofNullable(partitions.get(key))
                .orElseThrow(() -> new IllegalArgumentException("Invalid group " + key))
                .limit;
    }
    
    synchronized int getBusyCount() {
        return busy;
    }

    @Override
    public String toString() {
        return "LookupPartitionedStrategy [partitions=" + partitions + ", unknownPartition=" + unknownPartition
                + ", limit=" + limit + "]";
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy