I'm relatively new to Spring-Boot + resilience4j and I'm trying to create a Retry object using the config in my .yml file. Currently I'm trying to decorate a Mono with very similar syntax to what is given in the docs:
Retry retry = Retry.of("backendName", sampleRetryConfig);
Mono.fromCallable(backendService::doSomething)
.transformDeferred(RetryOperator.of(retry))
In the above code snippet I'm explicitly declaring the sampleRetryConfig in the code and using that to create my Retry, but is there a way for me to create the Retry object using the RetryConfig pulled from my .yml file?
resilience4j.retry:
instances:
apiRetry:
maxAttempts: 3
waitDuration: 2s
enableExponentialBackoff: true
ignoreExceptions:
- example.exceptions
Support seems to be there for using the #Retry annotation, but I haven't found anything about support for what I'm trying to do.
Late answer but here's what I ended up doing. Using the RetryRegistry and declaring the Retry objects as beans I was able to make it work.
Here's the contents of the .yml
resilience4j:
retry:
api-path:
maxAttempts: 1
waitDuration: 1s
ignoreExceptions:
And the class where the retry beans were created:
private final RetryRegistry retryRegistry;
#Bean
public Retry apiPathRetry() {
return retryRegistry.retry("api-path");
}
Then finally, using the objects in a class.
return Mono.fromSupplier(() -> method)
.flatMap(genericData-> {business logic})
.transformDeferred(RetryOperator.of(apiPathRetry));
Related
I am using spring-cloud-stream-binder-kafka and have implemented stateful retry using DefaultErrorHandler, I found that by enabling deliveryAttemptHeader of container properties I can access the retry count or deliveryAttempt count from message header but I am not able to enable it.
I tried to set the value to true as below
#Bean
public ContainerCustomizer<String, Message, ConcurrentMessageListenerContainer<String, Message>> containerCustomizer(
ConcurrentKafkaListenerContainerFactory<String, Message> factory) {
ContainerCustomizer<String, Message, ConcurrentMessageListenerContainer<String, Message>> custCustomizer = container -> {
container.getContainerProperties().setDeliveryAttemptHeader(true);
};
factory.setContainerCustomizer(custCustomizer);
return custCustomizer;
}
With this configuration when I start the application and debug KafkaMessageListenerContainer.java#L1078 I still see that deliveryAttemptHeader is disabled, and also the ContainerCustomizer instance that I have created is also not getting called.
spring-cloud-stream does not use Boot's container factory; use its ListenerContainerCustomizer instead.
https://docs.spring.io/spring-cloud-stream/docs/current/reference/html/spring-cloud-stream.html#_advanced_consumer_configuration
I'm using Spring Cloud Circuit Breaker 2.0.0 (resilience4j implementation) for circuit breaking and timeouts in my application. I've created the following configuration:
#Bean
public Customizer<Resilience4JCircuitBreakerFactory> defaultCustomizer() {
return factory ->
factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(10)).build())
.circuitBreakerConfig(..)
.build());
}
Now I want to write an integration test to verify that my behavior is correct when a timeout occurs. For this to work, I'd like to temporarily change the timeout duration specified in the configuration above to something like 1 millisecond instead of 10 seconds.
So my question is: How can I change the value of the timeout of the TimeLimiterConfig(temporarily) when I'm writing a Spring Boot integration test?
You can use the #Value Spring annotation that retrieves the value at a configuration file from your resource folder src/main/resources/common.properties.
#Bean
public Customizer<Resilience4JCircuitBreakerFactory> defaultCustomizer(
#Value("${duration.milli:600}") int durationMilli) {
return factory ->
factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofMilli(durationMilli)).build())
.circuitBreakerConfig(..)
.build());
}
Then you set the value at src/main/resources/common.properties
duration.milli=600
When you are doing your test you can configure another resource file at the test folder src/test/resources/common.properties with a different value.
duration.milli=1
I have a Spring Cloud Stream application that processes (credit card) events -- some of which are processed synchronously and some asynchronously. I came up with roughly the following in Kotlin:
spring-cloud-starter-stream-rabbit = 3.1.0
#Service
class CardEventProcessor(
private val streamBridge: StreamBridge,
) : Consumer<AsyncCardEvent> {
fun process(cardEvent: SyncCardEvent): Result { return businessLogic() }
fun processAsynchronously(cardEvent: AsyncCardEvent) {
streamBridge.send("cardEventProcessor-out-0", cardEvent)
}
override fun accept(cardEvent: AsyncCardEvent) { businessLogic() }
}
and have configured it like so:
rabbitmq: ...
cloud:
stream:
bindings:
cardEventProcessor-in-0:
destination: cardevents
group: CardEventProcessor
cardEventProcessor-out-0:
destination: cardevents
It all seems to work fine, except in integration tests the processing fails after the second async card event. I was able to debug / reduce the issue down to two handlers being registered in UnicastingDispatcher, which has a round robin strategy: one for TestChannelBinder and another for OutputDestination$lamdba.
This is what my integration test class looks like:
#SpringBootTest
#Transactional
#AutoConfigureMockMvc
#AutoConfigureEmbeddedDatabase
#Import(TestChannelBinderConfiguration::class)
class IntegrationTests {
#Test
fun `Use case one`() {
sendFirstAsyncRequest() // processed correctly in CardEventProcessor.accept()
sendSecondAsyncRequest() // message never arrives in CardEventProcessor.accept()
}
}
I was following the Testing section in Spring cloud stream docs and can't figure out what I'm missing to get this working. The example there is a Function<> not a Consumer<> and I produce within the same #Service class as the consumer (because the queue is just an implementation detail to solve async, not the typical use case of queues between micro-services) but as far as I understand that should work, and does in fact work when not running as integration test.
I saw Disable Spring Cloud Stream Rabbit for tests but didn't want to depend on the deprecated spring-cloud-stream-test-support and the other two suggestions didn't work either. Any ideas?
This is my configuration file.
resilience4j.circuitbreaker:
instances:
backendB:
registerHealthIndicator: true
slidingWindowSize: 10
minimumNumberOfCalls: 10
permittedNumberOfCallsInHalfOpenState: 3
waitDurationInOpenState: 5s
failureRateThreshold: 50
eventConsumerBufferSize: 10
I am trying to create bean of my feign client using Resilience4jFeign but the circuit breaker object is initialized with default configuration as the name suggest CircuitBreaker.ofDefaults. I can't find any ways to get my instance of circuit breaker from configuration to an object.
#Bean
public CommunicationServiceProxy communicationServiceProxy(){
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("backendB");
FeignDecorators decorators = FeignDecorators.builder()
.withCircuitBreaker(circuitBreaker)
.withFallbackFactory(CommunicationFallBack::new)
.build();
return Resilience4jFeign
.builder(decorators)
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
.target(CommunicationServiceProxy.class, "http://localhost:8081");
}
I found the solution and it was using the CircuitBreakerRegistry.
Instead of creating registry using
CircuitBreakerRegistry.ofDefaults()
one need to autowired them.
#Autowired
private CircuitBreakerRegistry circuitBreakerRegistry;
I am trying to configure the LoggingMeterRegistry to log metrics for my Spring Boot 2.1.6 application. I want the metrics to be logged every hour.
In my application.yml, I've the following configured
management:
metrics:
export:
logging:
enabled: true
step: 60m
But in the logs I see the metrics being logged every minute. I've tried the other variation for the property key as well e.g.
management.metrics.export.logging:
enabled: true
step: 60m
I have also tried various formats for the duration string e.g. 1h, PT60M but with no success. The metrics are logged at 1 minute intervals.
I was looking at the code here StepDurationConverterTest and here StepDurationConverter that converts the step duration String to a Duration object and looks like both formats 60m and 1h should work.
Any ideas why I can't seem to change the logging interval?
I think the problem here is there's no
org.springframework.boot.actuate.autoconfigure.metrics.export.logging
package like there is for other MeterRegistrys (eg org.springframework.boot.actuate.autoconfigure.metrics.export.jmx).
Ie theres no auto configuration for the properties in Spring Boot. This is probably because the LoggingMeterRegistry is marked as #Incubating
You need to manually configure the LoggingMeterRegistry as a bean and create your own #ConfigurationProperties LoggingProperties and LoggingPropertiesConfigAdapter to get this to work. Or just hardcode the step period you want.
To configure step count duration in micrometer:
Please follow below step:
#Configuration
public class LoggingMeterRegistryConfig {
#Bean
public LoggingMeterRegistry loggingMeterRegistry() {
LoggingRegistryConfig config = new LoggingRegistryConfig() {
#Override
public String get(String s) {
return null;
}
#Override
public Duration step() {
return Duration.ofMinutes(2);
}
};
return LoggingMeterRegistry.builder(config).clock(Clock.SYSTEM).threadFactory(new NamedThreadFactory("logging-metrics-publisher")).build();
}
}
The following #Bean supplies config from Spring Environment allowing you to specify a property logging.step: 1h to get your desired period.
#Bean
LoggingMeterRegistry loggingMeterRegistry(Environment env) {
LoggingRegistryConfig springBasedConfig = prop -> env.getProperty(prop, String.class);
return new LoggingMeterRegistry(springBasedConfig, Clock.SYSTEM);
}