【OpenFeign 】OpenFeign 下的重试机制

1  前言

服务间的调用,大家可能会用到 OpenFeign 方式。那么当被调用服务可能会因为某种情况导致调用失败(这个失败可能好似下游服务重启了或者超时断开等)的情况下,我们想重试的情况下该怎么做呢?本节我们就来看看。

2  环境准备

首先准备一下,我这里有两个服务,一个是 demo 一个是 virtuous,本地启动的 Eureka。

一个简单的调用过程,我这里简单画个图描述一下:

Virutous 服务通过 TestFeign 继而调用到 Demo 服务的 testFeign 接口。

3  重试

那我们看看这个重试,重试的话怎么让调用失败呢?

(1)可以在 Demo 服务接口里让他睡眠,触发 OpenFeign 的响应超时

# 响应超时时间
feign.client.config.default.read-timeout=1000

(2)可以把 Demo 服务停掉,这样就会导致 OpenFeign 连接超时 ?是这样么?其实不会,因为连接超时,意味着最起码我能去连接或者说是在连接,而停掉 Demo 服务意味着压根连接不上比如我们常见的 Connection Refused 甚至 Load balancer does not have available server for client: demo。所以简单的停掉并不会触发连接超时。至于怎么能触发可能就是那种 Demo 服务比较频繁,并发量大的情况下吧,这个我不太敢断言,还请有知道的小伙伴指点告知。

# 连接超时时间
feign.client.config.default.connect-timeout=1000

那我们这里就用睡眠来触发响应超时吧,响应超时设置 1秒,Demo 服务睡眠 2秒,然后就会出现 Read Timeout:

OpenFeign 默认情况下是不会自动开启超时重试的,要想重试的话,有两种方式:

(1)启用默认的超时重试,在 Retryer 接口中有一个默认的重试器,但是在重试的过程中你想看这是重试第几次等看到一些自定义的行为是不行的,所以可以用第二种方式自定义重试器

(2)自定义重试器,实现 Retryer 接口即可

3.1  启用默认的重试器

简单一点儿的我们可以直接在配置上设置默认的重试器:

feign.client.config.default.retryer=feign.Retryer.Default

但是这种默认的是 间隔100毫秒,重试5次,如果你想改变默认重试器的间隔等,可以这样:

@Configuration
public class RetryConfig {
    @Bean
    public Retryer retryer() {
        return new Retryer.Default(
                1000,   // 重试间隔时间
                1000,       // 最大重试间隔时间
                3           // 最大重试次数
        );     
    }
}

看效果总共耗时 8秒,第一次耗时1秒触发超时重试间隔 1秒,第3秒发送第一次重试,间隔 1秒,第5秒发送第二次重试,间隔1秒,第7秒发送第三次重试还超时达到最大重试次数,第8秒返回。

3.2  自定义重试器

这里我们直接在默认的基础上加上一点日志信息:

package com.virtuous.demo.config;
 
import feign.RetryableException;
import feign.Retryer;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

/**
 * @author kuku
 */
@Slf4j
public class VirtuousFeignRetryer extends Retryer.Default {

    private final int maxAttempts;
    private final long period;
    private final long maxPeriod;
    int attempt;
    long sleptForMillis;

    public VirtuousFeignRetryer() {
        // 默认间隔 1秒 最大间隔 1秒 重试3次
        this(1000L, TimeUnit.SECONDS.toMillis(1L), 3);
    }

    public VirtuousFeignRetryer(long period, long maxPeriod, int maxAttempts) {
        this.period = period;
        this.maxPeriod = maxPeriod;
        this.maxAttempts = maxAttempts;
        this.attempt = 1;
    }

    protected long currentTimeMillis() {
        return System.currentTimeMillis();
    }

    public void continueOrPropagate(RetryableException e) {
        log.info("当前重试:第{}次,最大重试:{}", this.attempt, this.maxAttempts);
        if (this.attempt++ >= this.maxAttempts) {
            throw e;
        } else {
            long interval;
            if (e.retryAfter() != null) {
                interval = e.retryAfter().getTime() - this.currentTimeMillis();
                if (interval > this.maxPeriod) {
                    interval = this.maxPeriod;
                }

                if (interval < 0L) {
                    return;
                }
            } else {
                interval = this.nextMaxInterval();
            }

            try {
                Thread.sleep(interval);
            } catch (InterruptedException var5) {
                Thread.currentThread().interrupt();
                throw e;
            }

            this.sleptForMillis += interval;
        }
    }

    long nextMaxInterval() {
        long interval = (long)((double)this.period * Math.pow(1.5, (double)(this.attempt - 1)));
        return interval > this.maxPeriod ? this.maxPeriod : interval;
    }

    public Retryer clone() {
        return new VirtuousFeignRetryer(this.period, this.maxPeriod, this.maxAttempts);
    }
}

最后修改配置:

feign.client.config.default.retryer=com.virtuous.demo.config.VirtuousFeignRetryer

看效果:

4  小结

好啦,本节我们简单尝试了下 OpenFeign 的重试,至于原理的话,我们下节空了看,有理解不对的地方,欢迎指正。

posted @ 2024-09-07 17:25  酷酷-  阅读(367)  评论(0编辑  收藏  举报