Skip to content

Commit

Permalink
Merge pull request #1235 from gpradeepkrishna/feature/add-ability-to-…
Browse files Browse the repository at this point in the history
…configure-failure-rate-threshold-for-circuit-breakers

add ability to configure failure rate threshold in circuit breakers
  • Loading branch information
fatroom authored Mar 7, 2022
2 parents e57aab9 + 129842e commit 83b408f
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 1 deletion.
2 changes: 2 additions & 0 deletions riptide-spring-boot-autoconfigure/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ riptide:
circuit-breaker:
enabled: true
failure-threshold: 3 out of 5
failure-rate-threshold: 3 out of 5 in 5 seconds
delay: 30 seconds
success-threshold: 5 out of 5
backup-request:
Expand Down Expand Up @@ -366,6 +367,7 @@ For a complete overview of available properties, they type and default value ple
| `│   ├── circuit-breaker` | | |
| `│   │   ├── enabled` | `boolean` | `false` |
| `│   │   ├── failure-threshold` | `Ratio` | none |
| `│   │   ├── failure-rate-threshold` | `RatioInTimeSpan` | none |
| `│   │   ├── delay` | `TimeSpan` | no delay |
| `│   │   └── success-threshold` | `Ratio` | `failure-threshold` |
| `│   ├── connections` | | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ private static CircuitBreaker merge(final CircuitBreaker base, final CircuitBrea
return new CircuitBreaker(
either(base.getEnabled(), defaults.getEnabled()),
either(base.getFailureThreshold(), defaults.getFailureThreshold()),
either(base.getFailureRateThreshold(), defaults.getFailureRateThreshold()),
either(base.getDelay(), defaults.getDelay()),
either(base.getSuccessThreshold(), defaults.getSuccessThreshold())
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ public static CircuitBreaker<ClientHttpResponse> createCircuitBreaker(
Optional.ofNullable(client.getCircuitBreaker().getFailureThreshold())
.ifPresent(threshold -> threshold.applyTo(breaker::withFailureThreshold));

Optional.ofNullable(client.getCircuitBreaker().getFailureRateThreshold())
.ifPresent(threshold -> threshold.applyTo(breaker::withFailureRateThreshold));

Optional.ofNullable(client.getCircuitBreaker().getDelay())
.ifPresent(delay -> delay.applyTo(breaker::withDelay));

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.zalando.riptide.autoconfigure;

import lombok.Getter;

import java.time.Duration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Getter
final class RatioInTimeSpan {

private static final Pattern PATTERN = Pattern.compile("(?<ratio>.*)? in (?<timespan>.*)?");

private final Ratio ratio;
private final TimeSpan timeSpan;

// used by SnakeYAML
@SuppressWarnings("unused")
public RatioInTimeSpan(String value) {
this(RatioInTimeSpan.valueOf(value));
}

private RatioInTimeSpan(Ratio ratio, TimeSpan timeSpan) {
this.ratio = ratio;
this.timeSpan = timeSpan;
}

private RatioInTimeSpan(RatioInTimeSpan ratioInTimeSpan) {
this(ratioInTimeSpan.ratio, ratioInTimeSpan.timeSpan);
}

void applyTo(RatioInTimeSpanConsumer consumer) {
consumer.accept(ratio.getAmount(), ratio.getTotal(), timeSpan.toDuration());
}

public static RatioInTimeSpan valueOf(String value) {
final Matcher matcher = PATTERN.matcher(value);

if (!matcher.matches()) {
throw new IllegalArgumentException("'" + value + "' is not a valid ratio in timespan");
}

final Ratio ratio = Ratio.valueOf(matcher.group("ratio"));
final TimeSpan timeSpan = TimeSpan.valueOf(matcher.group("timespan"));
return new RatioInTimeSpan(ratio, timeSpan);
}

interface RatioInTimeSpanConsumer {
void accept(int amount, int total, Duration duration);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public static final class Defaults {
new Backoff(false, null, null, null), -1, TimeSpan.of(5, SECONDS), null, null);

@NestedConfigurationProperty
private CircuitBreaker circuitBreaker = new CircuitBreaker(false, null, TimeSpan.of(0, SECONDS), null);
private CircuitBreaker circuitBreaker = new CircuitBreaker(false, null, null, TimeSpan.of(0, SECONDS), null);

@NestedConfigurationProperty
private BackupRequest backupRequest = new BackupRequest(false, null);
Expand Down Expand Up @@ -313,6 +313,7 @@ public static final class Backoff {
public static final class CircuitBreaker {
private Boolean enabled;
private Ratio failureThreshold;
private RatioInTimeSpan failureRateThreshold;
private TimeSpan delay;
private Ratio successThreshold;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.zalando.riptide.autoconfigure;

import org.junit.jupiter.api.Test;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;

final class RatioInTimeSpanTest {

@Test
void shouldParseRatioInTimeSpan() {
final RatioInTimeSpan ratioInTimeSpan = RatioInTimeSpan.valueOf("3 out of 5 in 5 seconds");

assertThat(ratioInTimeSpan.getRatio().getAmount(), is(3));
assertThat(ratioInTimeSpan.getRatio().getTotal(), is(5));
assertThat(ratioInTimeSpan.getTimeSpan().toDuration(), is(Duration.ofSeconds(5)));
}

@Test
void shouldParseUsingConstructor() {
final RatioInTimeSpan ratioInTimeSpan = new RatioInTimeSpan("4 / 10 in 15 minutes");

assertThat(ratioInTimeSpan.getRatio().getAmount(), is(4));
assertThat(ratioInTimeSpan.getRatio().getTotal(), is(10));
assertThat(ratioInTimeSpan.getTimeSpan().toDuration(), is(Duration.ofMinutes(15)));
}

@Test
void shouldApplyTo() {
final RatioInTimeSpan ratioInTimeSpan = RatioInTimeSpan.valueOf("4 in 15 minutes");
final Map<String, Object> consumerParameters = new HashMap<>();

ratioInTimeSpan.applyTo((amount, total, duration) -> {
consumerParameters.put("amount", amount);
consumerParameters.put("total", total);
consumerParameters.put("duration", duration);
});

assertThat(consumerParameters, hasEntry("amount", 4));
assertThat(consumerParameters, hasEntry("total", 4));
assertThat(consumerParameters, hasEntry("duration", Duration.ofMinutes(15)));
}

@Test
void shouldFailOnUnsupportedFormat() {
assertThrows(IllegalArgumentException.class, () -> RatioInTimeSpan.valueOf("a lot out of many"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,9 @@ riptide:
retry:
enabled: true
max-retries: 2
failure-rate-test:
base-url: http://example.com/failure-rate-test
circuit-breaker:
enabled: true
failure-rate-threshold: 3 in 5 seconds
success-threshold: 1

0 comments on commit 83b408f

Please sign in to comment.