
com.netflix.concurrency.limits.strategy.PredicatePartitionStrategy Maven / Gradle / Ivy
package com.netflix.concurrency.limits.strategy;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
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;
/**
* Concurrency limiter that guarantees a certain percentage of the limit to specific callers
* while allowing callers to borrow from underutilized callers.
*
* Callers are identified by their index into an array of percentages passed in during initialization.
* A percentage of 0.0 means that a caller may only use excess capacity and can be completely
* starved when the limit is reached. A percentage of 1.0 means that the caller
* is guaranteed to get the entire limit.
*
* grpc.server.call.inflight (group=[a, b, c])
* grpc.server.call.limit (group=[a,b,c])
*/
public final class PredicatePartitionStrategy implements Strategy {
private static final String PARTITION_TAG_NAME = "partition";
public static class Builder {
private final List> partitions = new ArrayList<>();
private MetricRegistry registry = EmptyMetricRegistry.INSTANCE;
public Builder metricRegistry(MetricRegistry registry) {
this.registry = registry;
return this;
}
public Builder add(String name, Double pct, Predicate predicate) {
partitions.add(new Partition(name, pct, predicate));
return this;
}
public PredicatePartitionStrategy build() {
return new PredicatePartitionStrategy(this);
}
public boolean hasPartitions() {
return !partitions.isEmpty();
}
}
public static Builder newBuilder() {
return new Builder();
}
private final List> partitions;
private int busy = 0;
private int limit = 0;
private PredicatePartitionStrategy(Builder builder) {
Preconditions.checkArgument(builder.partitions.stream().map(Partition::getPercent).reduce(0.0, Double::sum) <= 1.0,
"Sum of percentages must be <= 1.0");
this.partitions = new ArrayList<>(builder.partitions);
this.partitions.forEach(partition -> partition.createMetrics(builder.registry));
builder.registry.registerGauge(MetricIds.LIMIT_GUAGE_NAME, this::getLimit);
}
@Override
public synchronized Token tryAcquire(T type) {
for (final Partition partition : partitions) {
if (partition.predicate.test(type)) {
if (busy >= limit && partition.isLimitExceeded()) {
break;
}
busy++;
partition.acquire();
return Token.newAcquired(busy, () -> releasePartition(partition));
}
}
return Token.newNotAcquired(busy);
}
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(partition -> partition.updateLimit(newLimit));
}
}
public synchronized int getLimit() {
return limit;
}
private static class Partition {
private final double percent;
private final Predicate predicate;
private final String name;
private SampleListener inflightDistribution;
private int limit;
private int busy;
public Partition(String name, double pct, Predicate predicate) {
this.name = name;
this.percent = pct;
this.predicate = predicate;
}
public void createMetrics(MetricRegistry registry) {
this.inflightDistribution = 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++;
inflightDistribution.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(int index) {
Preconditions.checkArgument(index >= 0 && index < partitions.size(), "Invalid bin index " + index);
return partitions.get(index).busy;
}
synchronized int getBinLimit(int index) {
Preconditions.checkArgument(index >= 0 && index < partitions.size(), "Invalid bin index " + index);
return partitions.get(index).limit;
}
synchronized public int getBusyCount() {
return busy;
}
@Override
public String toString() {
final int maxLen = 10;
return "PercentageStrategy [" + partitions.subList(0, Math.min(partitions.size(), maxLen)) + "]";
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy