Tech Me More

To quench our thirst of sharing knowledge about our day to day experience & solution to techincal problems we face in our projects.

Advertise with us !
Send us an email at diehardtechy@gmail.com

Friday, May 15, 2020

OpenFeign : retry request based on custom HTTP response status


In the current distributed microservices architecture system while communicating with other microservices, we use multiple web service clients like feign, openfeign, rest template etc.  

Spring cloud uses openfeign under the hood. Graceful error handling is not only the need but the necessity for a robust system. In many instances, a microservice may return an unexpected response with response code like 40X, 50X, etc which means something has been messed up by end-user or there is a system error. In a likely scenario where you know the current error response may go away if you retry the request. 

Eg. en endpoint with /getDetails was throwing 50x error but after some time it will return the SUCCESS response. This can happen due to multiple factors like resource unavailability, client-side error, networking, etc. 


To overcome this problem programmer want to retry the request before throwing an exception. 

feign.codec.ErrorDecoder
Error decoder is the feign provided way to decode the error response. It has one method of

public Exception decode(String methodKey, Response response) 

the return type of this method is runtime Exception. if the exception is of type feign.RetryableException then feigns will retry this request. 

We can implement ErrorDecoder and provide our own implementation to this method. an example of this is listed below. 

package com.dht;

import java.util.Date;
import org.springframework.http.HttpStatus;
import feign.Response;
import feign.RetryableException;
import feign.codec.ErrorDecoder;
import lombok.extern.slf4j.Slf4j;

/**
 * Custom Error decoder for feign Retries the request again if response code is
 * 404
 */

@Slf4j
public class FeignErrorDecoder implements ErrorDecoder {

    private final ErrorDecoder defaultErrorDecoder = new Default();

    @Override
    public Exception decode(String methodKey,
                            Response response) {

        if (response.status() == HttpStatus.NOT_FOUND.value()) {
            log.info("Error while executing " + methodKey + " Error code "
                        + HttpStatus.NOT_FOUND);
            return new RetryableException(response.status(), methodKey, null,
                        new Date(System.currentTimeMillis()),
                        response.request());
        }
        return defaultErrorDecoder.decode(methodKey, response);

    }
}

The above decoder will return the Retryable exception if the response code is 404. We can customize it for our own purpose or logic. Please remember to pass response.request() and not null in the last parameter. 

We can also customize how many times we want to retry and the interval before retrying. Feign comes with feign. Retryer interface which we can override and provide our own implementation to customize the retry behavior. See the below example.


import java.util.concurrent.TimeUnit;

import com.vmware.eso.ops.apitest.constants.CasaConstants;

import feign.RetryableException;
import feign.Retryer;
import lombok.extern.slf4j.Slf4j;

/**
 * Custom feign retryer.
 */
@Slf4j
public class FeignRetyer implements Retryer {

    private final int maxAttempts;
    private final long backoff;
    int attempt;

    /**
     * Waits for 10
     * second before retrying.
     */
    public FeignRetyer() {
        this(10000, 5);
    }

    public FeignRetyer(long backoff,
                       int maxAttempts) {
        this.backoff = backoff;
        this.maxAttempts = maxAttempts;
        this.attempt = 1;
    }

    public void continueOrPropagate(RetryableException e) {

        if (attempt++ >= maxAttempts) {
            throw e;
        }

        try {
            TimeUnit.MILLISECONDS.sleep(backoff);
        } catch (InterruptedException ex) {

        }

        log.info("Retrying: " + e.request().url() + " attempt " + attempt);
    }

    @Override
    public Retryer clone() {
        return new FeignRetyer(backoff, maxAttempts);
    }
}

In the above example, we are retrying 5 times before throwing the exception and the interval between the request is 10 seconds.

Finally, we need to register both the above classes with Feign configuration bean. We can do this as follows.

    @Bean
    public FeignErrorDecoder feignErrorDecoder() {
        return new FeignErrorDecoder();
    }

    @Bean
    public Retryer retryer() {
        return new FeignRetyer();
    }

as per the above example, we will see requests being tried 5 times before failing. 

Happy retrying & coding. 

No comments: