Skip to content

Commit

Permalink
Unifying circuit breaker abstractions (#553)
Browse files Browse the repository at this point in the history
* setup resilience4j

* specifying circuit breaker strategy name

* creating agnostic interface to support multiple circuit breaker strategy types

* fixme

* sppliting circuit breaker factory

* refactoring is done

* test is passing

* test is passing

* test is passing

* refactoring package

* [Gradle Release Plugin] - new version commit:  '3.25.11-snapshot'.

* release notes

* [Gradle Release Plugin] - new version commit:  '3.25.12-snapshot'.

* release notes

* fixme note

* fixme note

* refactoring to support multiple delegates

* refactoring name

* refactoring

* implementing non resilient strategy

* refactoring and test

* removing unnecessary test

* refactoring and fixing test

* release notes

* [Gradle Release Plugin] - new version commit:  '3.25.13-snapshot'.
  • Loading branch information
mageddo authored Sep 2, 2024
1 parent 3fa61af commit 1b8cf1c
Show file tree
Hide file tree
Showing 30 changed files with 299 additions and 211 deletions.
3 changes: 3 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 3.25.13
* Unifying circuit breaker abstractions #553

## 3.25.12
* Creating an abstraction of circuit breaker implementation #533

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version=3.25.12-snapshot
version=3.25.13-snapshot

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.mageddo.dnsproxyserver.config;

import com.mageddo.dnsproxyserver.solver.remote.circuitbreaker.application.CircuitBreakerDelegateNonResilient;
import com.mageddo.dnsproxyserver.solver.remote.circuitbreaker.application.CircuitBreakerDelegateStaticThresholdFailsafe;

public interface CircuitBreakerStrategyConfig {

Name name();

enum Name {
/**
* @see CircuitBreakerDelegateStaticThresholdFailsafe
*/
STATIC_THRESHOLD,

CANARY_RATE_THRESHOLD,

/**
* @see CircuitBreakerDelegateNonResilient
*/
NON_RESILIENT,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public void resetConfigFile() {
}

@JsonIgnore
public CircuitBreakerStrategy getSolverRemoteCircuitBreakerStrategy() {
public CircuitBreakerStrategyConfig getSolverRemoteCircuitBreakerStrategy() {
if (this.solverRemote == null) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.mageddo.dnsproxyserver.config;

public class NonResilientCircuitBreakerStrategyConfig implements CircuitBreakerStrategyConfig {
@Override
public Name name() {
return Name.NON_RESILIENT;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ public class SolverRemote {

private Boolean active;

private CircuitBreakerStrategy circuitBreaker;
private CircuitBreakerStrategyConfig circuitBreaker;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

@Value
@Builder
public class StaticThresholdCircuitBreakerStrategy implements CircuitBreakerStrategy {
public class StaticThresholdCircuitBreakerStrategyConfig implements CircuitBreakerStrategyConfig {

/**
* See {@link dev.failsafe.CircuitBreakerBuilder#withFailureThreshold(int, int)}
Expand All @@ -26,12 +26,12 @@ public class StaticThresholdCircuitBreakerStrategy implements CircuitBreakerStra
*/
private Duration testDelay;

public static StaticThresholdCircuitBreakerStrategy empty() {
return StaticThresholdCircuitBreakerStrategy.builder().build();
public static StaticThresholdCircuitBreakerStrategyConfig empty() {
return StaticThresholdCircuitBreakerStrategyConfig.builder().build();
}

@Override
public Name type() {
public Name name() {
return Name.STATIC_THRESHOLD;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.mageddo.dnsproxyserver.config.application;

import com.mageddo.dnsproxyserver.config.CircuitBreakerStrategyConfig;
import com.mageddo.dnsproxyserver.config.Config;
import com.mageddo.dnsproxyserver.config.SolverRemote;
import com.mageddo.dnsproxyserver.config.dataprovider.ConfigDAO;
import com.mageddo.dnsproxyserver.config.mapper.ConfigMapper;
import org.apache.commons.lang3.ClassUtils;
Expand Down Expand Up @@ -29,6 +31,12 @@ public Config findCurrentConfig() {
return ConfigMapper.mapFrom(this.findConfigs());
}

public SolverRemote findCurrentConfigRemote(){
return this.findCurrentConfig()
.getSolverRemote()
;
}

List<Config> findConfigs() {
return this.findConfigDaos()
.map(ConfigDAO::find)
Expand All @@ -46,4 +54,9 @@ public List<String> findConfigNames(){
.map(ClassUtils::getSimpleName)
.toList();
}

public CircuitBreakerStrategyConfig findCurrentConfigCircuitBreaker() {
return this.findCurrentConfigRemote()
.getCircuitBreaker();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.mageddo.dnsproxyserver.config.dataprovider.mapper;

import com.mageddo.dnsproxyserver.config.StaticThresholdCircuitBreakerStrategy;
import com.mageddo.dnsproxyserver.config.StaticThresholdCircuitBreakerStrategyConfig;
import com.mageddo.dnsproxyserver.config.Config;
import com.mageddo.dnsproxyserver.config.SolverRemote;
import com.mageddo.dnsproxyserver.config.dataprovider.vo.ConfigJson;
Expand Down Expand Up @@ -65,7 +65,7 @@ static SolverRemote buildCompleteSolverRemote(ConfigJson json, ConfigJsonV2.Circ
.active(Booleans.reverseWhenNotNull(json.getNoRemoteServers()))
// fixme #533 need to create a dynamic json parser for different strategies,
// then a dynamic mapper to the solver remote
.circuitBreaker(StaticThresholdCircuitBreakerStrategy
.circuitBreaker(StaticThresholdCircuitBreakerStrategyConfig
.builder()
.failureThreshold(circuitBreaker.getFailureThreshold())
.failureThresholdCapacity(circuitBreaker.getFailureThresholdCapacity())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.mageddo.dnsproxyserver.config.mapper;

import com.mageddo.dnsproxyserver.config.StaticThresholdCircuitBreakerStrategy;
import com.mageddo.dnsproxyserver.config.StaticThresholdCircuitBreakerStrategyConfig;
import com.mageddo.dnsproxyserver.config.Config;
import com.mageddo.dnsproxyserver.config.SolverRemote;
import com.mageddo.dnsproxyserver.config.dataprovider.ConfigPropDAO;
Expand Down Expand Up @@ -74,8 +74,8 @@ private static Config buildDefault() {
.build();
}

public static StaticThresholdCircuitBreakerStrategy defaultCircuitBreaker() {
return StaticThresholdCircuitBreakerStrategy
public static StaticThresholdCircuitBreakerStrategyConfig defaultCircuitBreaker() {
return StaticThresholdCircuitBreakerStrategyConfig
.builder()
.failureThreshold(3)
.failureThresholdCapacity(10)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.mageddo.dnsproxyserver.config.validator;

import com.mageddo.dnsproxyserver.config.StaticThresholdCircuitBreakerStrategy;
import com.mageddo.dnsproxyserver.config.StaticThresholdCircuitBreakerStrategyConfig;
import org.apache.commons.lang3.Validate;

import static com.mageddo.dnsproxyserver.utils.Numbers.positiveOrNull;

public class CircuitBreakerValidator {
public static void validate(StaticThresholdCircuitBreakerStrategy circuit) {
public static void validate(StaticThresholdCircuitBreakerStrategyConfig circuit) {
Validate.notNull(positiveOrNull(circuit.getFailureThreshold()), genMsg("failure theshold must be a positive number"));
Validate.notNull(positiveOrNull(circuit.getSuccessThreshold()), genMsg("success theshold must be positive number"));
Validate.notNull(positiveOrNull(circuit.getFailureThresholdCapacity()), genMsg("success theshold capacity must be positive number"));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.mageddo.dnsproxyserver.config.validator;

import com.mageddo.dnsproxyserver.config.Config;
import com.mageddo.dnsproxyserver.config.StaticThresholdCircuitBreakerStrategy;
import com.mageddo.dnsproxyserver.config.StaticThresholdCircuitBreakerStrategyConfig;
import org.apache.commons.lang3.Validate;

public class ConfigValidator {
Expand All @@ -25,6 +25,6 @@ public static void validate(Config config) {
Validate.notNull(config.isSolverRemoteActive(), "Solver remote active");

// fixme #533 this could not work every time after new types be created, check it
CircuitBreakerValidator.validate((StaticThresholdCircuitBreakerStrategy) config.getSolverRemoteCircuitBreakerStrategy());
CircuitBreakerValidator.validate((StaticThresholdCircuitBreakerStrategyConfig) config.getSolverRemoteCircuitBreakerStrategy());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.stream.Stream;

import static com.mageddo.dns.utils.Messages.simplePrint;
Expand Down Expand Up @@ -92,7 +93,11 @@ Request buildRequest(Message query, int resolverIndex, StopWatch stopWatch, Reso

Result safeQueryResult(Request req) {
req.splitStopWatch();
return this.circuitBreakerService.safeHandle(req.getResolverAddress(), () -> this.queryResult(req));
return this.queryUsingCircuitBreaker(req, () -> this.queryResult(req));
}

Result queryUsingCircuitBreaker(Request req, Supplier<Result> sup) {
return this.circuitBreakerService.safeHandle(req.getResolverAddress(), sup);
}

Result queryResult(Request req) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

import com.mageddo.dnsproxyserver.solver.Resolver;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;

@Value
@Builder
public class ResolverStats {

@NonNull
private Resolver resolver;

private CircuitStatus circuitStatus;

public boolean isValidToUse() {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,16 +1,49 @@
package com.mageddo.dnsproxyserver.solver.remote.application;

import com.mageddo.commons.circuitbreaker.CircuitCheckException;
import com.mageddo.commons.circuitbreaker.CircuitIsOpenException;
import com.mageddo.dnsproxyserver.solver.remote.CircuitStatus;
import com.mageddo.dnsproxyserver.solver.remote.Result;
import com.mageddo.dnsproxyserver.solver.remote.application.failsafe.CircuitBreakerFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ClassUtils;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.net.InetSocketAddress;
import java.util.function.Supplier;

public interface CircuitBreakerService {

// fixme #533 esse padrão de strategy não é mais necessário aqui, foi movido para CircuitBreakerDelegate
// onde tem mais chances de reduzir duplicação
Result safeHandle(final InetSocketAddress resolverAddress, Supplier<Result> sup);
@Slf4j
@Singleton
@RequiredArgsConstructor(onConstructor = @__({@Inject}))
public class CircuitBreakerService {

CircuitStatus findCircuitStatus(InetSocketAddress resolverAddress);
private final CircuitBreakerFactory circuitBreakerFactory;

private String status;

public Result safeHandle(InetSocketAddress resolverAddress, Supplier<Result> sup) {
try {
return this.handle(resolverAddress, sup);
} catch (CircuitCheckException | CircuitIsOpenException e) {
final var clazz = ClassUtils.getSimpleName(e);
log.debug("status=circuitEvent, server={}, type={}", resolverAddress, clazz);
this.status = String.format("%s for %s", clazz, resolverAddress);
return Result.empty();
}
}

private Result handle(InetSocketAddress resolverAddress, Supplier<Result> sup) {
return this.circuitBreakerFactory.check(resolverAddress, sup);
}

public String getStatus() {
return this.status;
}

public CircuitStatus findCircuitStatus(InetSocketAddress resolverAddress) {
return this.circuitBreakerFactory.findStatus(resolverAddress);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.mageddo.dnsproxyserver.solver.remote.application;

import com.mageddo.commons.circuitbreaker.CircuitCheckException;
import com.mageddo.dnsproxyserver.config.StaticThresholdCircuitBreakerStrategy;
import com.mageddo.dnsproxyserver.config.StaticThresholdCircuitBreakerStrategyConfig;
import com.mageddo.dnsproxyserver.solver.remote.CircuitStatus;
import com.mageddo.dnsproxyserver.solver.remote.Result;
import com.mageddo.dnsproxyserver.solver.remote.mapper.CircuitBreakerStateMapper;
Expand All @@ -23,7 +23,7 @@ public class FailsafeCircuitBreakerFactory {
private final OnCacheMustBeFlushedEvent onCacheMustBeFlushedEvent;

public CircuitBreaker<Result> build(
InetSocketAddress address, StaticThresholdCircuitBreakerStrategy config
InetSocketAddress address, StaticThresholdCircuitBreakerStrategyConfig config
) {
return CircuitBreaker.<Result>builder()
.handle(CircuitCheckException.class)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package com.mageddo.dnsproxyserver.solver.remote.application.failsafe;

import com.mageddo.commons.lang.tuple.Pair;
import com.mageddo.dnsproxyserver.config.StaticThresholdCircuitBreakerStrategy;
import com.mageddo.dnsproxyserver.config.CircuitBreakerStrategyConfig;
import com.mageddo.dnsproxyserver.config.StaticThresholdCircuitBreakerStrategyConfig;
import com.mageddo.dnsproxyserver.config.application.ConfigService;
import com.mageddo.dnsproxyserver.solver.remote.CircuitStatus;
import com.mageddo.dnsproxyserver.solver.remote.Result;
import com.mageddo.dnsproxyserver.solver.remote.application.FailsafeCircuitBreakerFactory;
import com.mageddo.dnsproxyserver.solver.remote.circuitbreaker.application.CircuitBreakerDelegate;
import com.mageddo.dnsproxyserver.solver.remote.circuitbreaker.application.CircuitBreakerDelegateFailsafe;
import com.mageddo.dnsproxyserver.solver.remote.circuitbreaker.application.CircuitBreakerDelegateNonResilient;
import com.mageddo.dnsproxyserver.solver.remote.circuitbreaker.application.CircuitBreakerDelegateStaticThresholdFailsafe;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -37,18 +39,30 @@ public Result check(InetSocketAddress remoteAddress, Supplier<Result> sup) {
}

public CircuitBreakerDelegate findCircuitBreaker(InetSocketAddress address) {
final var strategy = this.findCircuitBreakerHotLoad(address);
return this.circuitBreakerMap.computeIfAbsent(address, addr -> strategy);
}

CircuitBreakerDelegate findCircuitBreakerHotLoad(InetSocketAddress address) {
final var config = this.findCircuitBreakerConfig();
return this.circuitBreakerMap.computeIfAbsent(
return switch (config.name()) {
case STATIC_THRESHOLD -> this.buildStaticThresholdFailSafeCircuitBreaker(address, config);
case NON_RESILIENT -> new CircuitBreakerDelegateNonResilient();
default -> throw new UnsupportedOperationException();
};
}

private CircuitBreakerDelegateStaticThresholdFailsafe buildStaticThresholdFailSafeCircuitBreaker(
InetSocketAddress address, CircuitBreakerStrategyConfig config
) {
return new CircuitBreakerDelegateStaticThresholdFailsafe(this.failsafeCircuitBreakerFactory.build(
address,
addr -> new CircuitBreakerDelegateFailsafe(this.failsafeCircuitBreakerFactory.build(addr, config))
);
(StaticThresholdCircuitBreakerStrategyConfig) config
));
}

StaticThresholdCircuitBreakerStrategy findCircuitBreakerConfig() {
// fixme #533 this could not work every time, check it
return (StaticThresholdCircuitBreakerStrategy) this.configService.findCurrentConfig()
.getSolverRemote()
.getCircuitBreaker();
CircuitBreakerStrategyConfig findCircuitBreakerConfig() {
return this.configService.findCurrentConfigCircuitBreaker();
}

public Pair<Integer, Integer> checkCreatedCircuits() {
Expand Down
Loading

0 comments on commit 1b8cf1c

Please sign in to comment.