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

io.github.devlibx.easy.messaging.kafka.producer.KafkaBasedResilientProducer Maven / Gradle / Ivy

The newest version!
package io.github.devlibx.easy.messaging.kafka.producer;

import io.gitbub.devlibx.easy.helper.map.StringObjectMap;
import io.gitbub.devlibx.easy.helper.metrics.IMetrics;
import io.gitbub.devlibx.easy.helper.string.StringHelper;
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import lombok.extern.slf4j.Slf4j;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

@Slf4j
public class KafkaBasedResilientProducer extends KafkaBasedProducer {
    private final CircuitBreaker circuitBreaker;

    public KafkaBasedResilientProducer(StringObjectMap config, StringHelper stringHelper, IMetrics metrics) {
        super(config, stringHelper, metrics);

        // How long to remain in open state when circuit opens (default = 10sec)
        int waitDurationInOpenState;
        if (config.containsKey("circuit-breaker.stay_in_open_state_on_error.ms")) {
            waitDurationInOpenState = config.getInt("circuit-breaker.stay_in_open_state_on_error.ms", 10 * 1000);
        } else if (config.containsKey("circuit-breaker-stay_in_open_state_on_error-ms")) {
            waitDurationInOpenState = config.getInt("circuit-breaker-stay_in_open_state_on_error-ms", 10 * 1000);
        } else {
            waitDurationInOpenState = 10 * 1000;
        }

        // Setup a circuit breaker with default settings
        CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
                .enableAutomaticTransitionFromOpenToHalfOpen()
                .waitDurationInOpenState(Duration.ofMillis(waitDurationInOpenState))
                .build();
        circuitBreaker = CircuitBreaker.of(config.getString("name") + "-circuit-breaker", circuitBreakerConfig);
    }

    @Override
    public boolean send(String key, Object value) {
        long start = System.currentTimeMillis();
        try {
            try {
                return circuitBreaker.decorateCallable(() -> KafkaBasedResilientProducer.super.send(key, value)).call();
            } catch (InternalErrorWrapper wrapperError) {
                throw wrapperError.e;
            }
        } catch (CallNotPermittedException e) {
            metrics.observe(metricsPrefix + "_failure_time_taken", (System.currentTimeMillis() - start));
            metrics.inc(metricsPrefix + "_failure");
            metrics.inc(metricsPrefix + "_failure_circuit_open");
            log.error("failed to send kafka message (CallNotPermittedException): topic={}, error={}", config.getString("name"), e.getMessage());
        } catch (Exception e) {
            metrics.observe(metricsPrefix + "_failure_time_taken", (System.currentTimeMillis() - start));
            metrics.inc(metricsPrefix + "_failure");
            log.error("circuit-open to send kafka message: topic={}, error={}", config.getString("name"), e.getMessage());
        }
        return false;
    }

    @Override
    protected void processError(Exception e) {
        throw new InternalErrorWrapper(e);
    }

    @Override
    protected void processErrorOnAsyncResponse(long duration, TimeUnit durationUnit, Throwable throwable) {
        try {
            circuitBreaker.onError(duration, durationUnit, throwable);
        } catch (Exception ignored) {
        }
    }

    private static class InternalErrorWrapper extends RuntimeException {
        private final Exception e;

        private InternalErrorWrapper(Exception e) {
            this.e = e;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy