16

I have a restful service calling an external service using Spring Cloud Feign client

@FeignClient(name = "external-service", configuration = FeignClientConfig.class)
public interface ServiceClient {

    @RequestMapping(value = "/test/payments", method = RequestMethod.POST)
    public void addPayment(@Valid @RequestBody AddPaymentRequest addPaymentRequest);

    @RequestMapping(value = "/test/payments/{paymentId}", method = RequestMethod.PUT)
    public ChangePaymentStatusResponse updatePaymentStatus(@PathVariable("paymentId") String paymentId,
            @Valid @RequestBody PaymentStatusUpdateRequest paymentStatusUpdateRequest);

}

I noticed the following failure 3-4 times in the last 3 months in my log file:

json.ERROR_RESPONSE_BODY:Connection refused executing POST http://external-service/external/payments json.message:Send Payment Add Payment Failure For other reason: {ERROR_RESPONSE_BODY=Connection refused executing POST http://external-service/external/payments, EVENT=ADD_PAYMENT_FAILURE, TRANSACTION_ID=XXXXXXX} {} json.EVENT:ADD_PAYMENT_FAILURE json.stack_trace:feign.RetryableException: Connection refused executing POST http://external-service/external/payments at feign.FeignException.errorExecuting(FeignException.java:67) at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:104) at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76) at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103)

Is it possible to add Spring Retry on a Feign client. What I wanted to annotate the addPayment operation with

@Retryable(value = {feign.RetryableException.class }, maxAttempts = 3, backoff = @Backoff(delay = 2000, multiplier=2))

But this is not possible, what other options do I have?

Jeff
  • 1,871
  • 1
  • 17
  • 28
Satya
  • 328
  • 1
  • 7
  • 19
  • According to the docs it is possible...https://cloud.spring.io/spring-cloud-netflix/multi/multi_retrying-failed-requests.html I can't get it working though. – notAChance Nov 06 '19 at 14:04

7 Answers7

22

You can add a Retryer in the FeignClientConfig

@Configuration
public class FeignClientConfig {

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

}

class Custom implements Retryer {

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

    public Custom() {
        this(2000, 3);
    }

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

    public void continueOrPropagate(RetryableException e) {
        if (attempt++ >= maxAttempts) {
            throw e;
        }

        try {
            Thread.sleep(backoff);
        } catch (InterruptedException ignored) {
            Thread.currentThread().interrupt();
        }
    }

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

Updated with sample Retryer example config based on the Retryer.Default.

Jeff
  • 1,871
  • 1
  • 17
  • 28
  • Can you point to the code sample showing custom Retryer? How to configure retry only for feign.RetryableException and max attempt = 3 and backoff period 2 sec between retries – Satya Nov 07 '17 at 15:25
  • 1
    You can just implement your own Retryer – Jeff Nov 07 '17 at 15:40
  • In this scenario, how many times the external API will be invoked inclding the retries ? Is it like Call-1 (Normal call in the beginning) + Call-2 (retry-1) + Call-3 (retry-2) + Call-4 (retry-3) OR is it like Call-1 (Normal call in the beginning) + Call-2 (retry-1) + Call-3 (retry-2) – Shanij P.S Jul 18 '23 at 13:37
3

If you are using ribbon you can set properties, you can use below properties for retry:

myapp.ribbon.MaxAutoRetries=5
myapp.ribbon.MaxAutoRetriesNextServer=5
myapp.ribbon.OkToRetryOnAllOperations=true

Note: "myapp" is your service id.

Checkout this Github implementation for working example

Yogi
  • 1,805
  • 13
  • 24
  • Only need retry for feign.RetryableException.class and only for external-service feign client. My application contains multiple feign clients. for ribbon settings can we specify the specific feign client and exception class? – Satya Nov 07 '17 at 15:11
  • For such specific case, you need to go for FeignConfig and attached it to you FeignClient class and perform the logic – Yogi Nov 07 '17 at 16:36
2

Just new a contructor Default

@Configuration
public class FeignClientConfig {
    @Bean
    public Retryer retryer() {
        return new Retryer.Default(100, 2000, 3);
    }
}
Toi Nguyen
  • 413
  • 1
  • 6
  • 17
0

Adding this if it can help someone. I was getting connection reset using feign, as some unknown process was running on that port. Try changing the port. Refer this to find the process running on a port

Deekshith Anand
  • 2,175
  • 1
  • 21
  • 24
0

I prepared a blog post about using Spring Retry with Feign Client methods. You may consider checking the Post. All steps have been explained in the post.

güven seckin
  • 99
  • 1
  • 4
0

This is my config. Test OK in spring boot 2.2.0.RELEASE spring cloud Hoxton.M3.

feign.hystrix.enabled=true
MY-SPRING-API.ribbon.MaxAutoRetries=2
MY-SPRING-API.ribbon.MaxAutoRetriesNextServer=2
MY-SPRING-API.ribbon.OkToRetryOnAllOperations=true
MY-SPRING-API.ribbon.retryableStatusCodes=404,500

feign.client.config.PythonPatentClient.connectTimeout=500
feign.client.config.PythonPatentClient.readTimeout=500

hystrix.command.PythonPatentClient#timeTest(String).execution.isolation.thread.timeoutInMilliseconds=5000

java code is :

@FeignClient(name = "MY-SPRING-API",configuration = {PythonPatentConfig.class},fallbackFactory = FallBack.class)
public interface PythonPatentClient 
@RequestLine("GET /test?q={q}")
    void timeTest(@Param("appNo") String q);

Controller is :

  @RequestMapping(value = "/test",method = {RequestMethod.POST,RequestMethod.GET})
    public Object test() throws InterruptedException {
        log.info("========important print enter test========");
         TimeUnit.SECONDS.sleep(10L);

pom.xml additon add:

<dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
   </dependency>

optional:

<dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
    </dependency>

@EnableRetry
@SpringBootApplication
public class ApiApplication

this is document :

https://docs.spring.io/spring-cloud-netflix/docs/2.2.10.RELEASE/reference/html/#retrying-failed-requests

https://github.com/spring-projects/spring-retry

https://github.com/spring-cloud/spring-cloud-netflix/

zy_sun
  • 175
  • 1
  • 11
-1

I resolved that by creating a wrapper on top of ServiceClient

@Configuration
public class ServiceClient {

@Autowired
ServiceFeignClient serviceFeignClient;

@Retryable(value = { ClientReprocessException.class }, maxAttemptsExpression = "#{${retryMaxAttempts}}", backoff = @Backoff(delayExpression = "#{${retryDelayTime}}"))

public void addPayment( AddPaymentRequest addPaymentRequest){
    return serviceFeignClient.addPayment(addPaymentRequest);
 }    
}
Vikky
  • 1,123
  • 14
  • 16