spring cloud学习(三)——feign 使用以及问题

本文介绍了Spring Cloud中Feign的使用,包括服务端的jar引入和客户端的配置,特别是针对Feign调用超时的问题进行了深入探讨。在遇到调用超时后,通过查看后台错误信息和源码分析,发现默认的超时时间和重试次数不满足需求。解决方案是调整客户端配置文件,设置合适的连接和超时时间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、服务端(server)

引入jar

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

demo

@FeignClient(name = "testDemo")
public interface PingFeignApi {
    @GetMapping(value = "/feign/v1.0/ping", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    Object ping();
}

2、客户端(client)配置

以下配置用于传递请求头信息

@Configuration
public class FeignConfig implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        HttpServletRequest request = SessionUtils.getRequest();
        if (request != null) {
            requestTemplate.header("Authorization", SessionUtils.getRequest().getHeader("Authorization"));
        }
    }
}

3、fegin 调用超时问题

3.1 后台显示如下:

Caused by: feign.RetryableException: Read timed out executing GET   
	at feign.FeignException.errorExecuting(FeignException.java:65) ~[feign-core-9.7.0.jar:na]
	at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:105) ~[feign-core-9.7.0.jar:na]
	at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:77) ~[feign-core-9.7.0.jar:na]
	at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:102) ~[feign-core-9.7.0.jar:na]
	at com.sun.proxy.$Proxy144.checkPermission(Unknown Source) ~[na:na]
	at com.honeywell.foxconn.usercenter.interceptor.AuthInterceptor.preHandle(AuthInterceptor.java:54) ~[UserCenterInterceptor-1.0-SNAPSHOT.jar:na]
	at org.springframework.web.servlet.HandlerExecutionChain.applyPreHandle(HandlerExecutionChain.java:136) ~[spring-webmvc-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1033) ~[spring-webmvc-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) ~[spring-webmvc-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) ~[spring-webmvc-5.1.6.RELEASE.jar:5.1.6.RELEASE]
	... 59 common frames omitted
Caused by: java.net.SocketTimeoutException: Read timed out
	at java.net.SocketInputStream.socketRead0(Native Method) ~[na:1.8.0_172]
	at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) ~[na:1.8.0_172]
	at java.net.SocketInputStream.read(SocketInputStream.java:171) ~[na:1.8.0_172]
	at java.net.SocketInputStream.read(SocketInputStream.java:141) ~[na:1.8.0_172]
	at java.io.BufferedInputStream.fill(BufferedInputStream.java:246) ~[na:1.8.0_172]
	at java.io.BufferedInputStream.read1(BufferedInputStream.java:286) ~[na:1.8.0_172]
	at java.io.BufferedInputStream.read(BufferedInputStream.java:345) ~[na:1.8.0_172]
	at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:735) ~[na:1.8.0_172]
	at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:678) ~[na:1.8.0_172]
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1587) ~[na:1.8.0_172]
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492) ~[na:1.8.0_172]
	at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480) ~[na:1.8.0_172]
	at feign.Client$Default.convertResponse(Client.java:150) ~[feign-core-9.7.0.jar:na]
	at feign.Client$Default.execute(Client.java:72) ~[feign-core-9.7.0.jar:na]
	at org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer.execute(FeignLoadBalancer.java:89) ~[spring-cloud-openfeign-core-2.0.2.RELEASE.jar:2.0.2.RELEASE]
	at org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer.execute(FeignLoadBalancer.java:55) ~[spring-cloud-openfeign-core-2.0.2.RELEASE.jar:2.0.2.RELEASE]
	at com.netflix.client.AbstractLoadBalancerAwareClient$1.call(AbstractLoadBalancerAwareClient.java:104) ~[ribbon-loadbalancer-2.2.5.jar:2.2.5]
	at com.netflix.loadbalancer.reactive.LoadBalancerCommand$3$1.call(LoadBalancerCommand.java:303) ~[ribbon-loadbalancer-2.2.5.jar:2.2.5]
	at com.netflix.loadbalancer.reactive.LoadBalancerCommand$3$1.call(LoadBalancerCommand.java:287) ~[ribbon-loadbalancer-2.2.5.jar:2.2.5]
	at rx.internal.util.ScalarSynchronousObservable$3.call(ScalarSynchronousObservable.java:231) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.internal.util.ScalarSynchronousObservable$3.call(ScalarSynchronousObservable.java:228) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.Observable.unsafeSubscribe(Observable.java:10327) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.internal.operators.OnSubscribeConcatMap$ConcatMapSubscriber.drain(OnSubscribeConcatMap.java:286) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.internal.operators.OnSubscribeConcatMap$ConcatMapSubscriber.onNext(OnSubscribeConcatMap.java:144) ~[rxjava-1.3.8.jar:1.3.8]
	at com.netflix.loadbalancer.reactive.LoadBalancerCommand$1.call(LoadBalancerCommand.java:185) ~[ribbon-loadbalancer-2.2.5.jar:2.2.5]
	at com.netflix.loadbalancer.reactive.LoadBalancerCommand$1.call(LoadBalancerCommand.java:180) ~[ribbon-loadbalancer-2.2.5.jar:2.2.5]
	at rx.Observable.unsafeSubscribe(Observable.java:10327) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.internal.operators.OnSubscribeConcatMap.call(OnSubscribeConcatMap.java:94) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.internal.operators.OnSubscribeConcatMap.call(OnSubscribeConcatMap.java:42) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.Observable.unsafeSubscribe(Observable.java:10327) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.internal.operators.OperatorRetryWithPredicate$SourceSubscriber$1.call(OperatorRetryWithPredicate.java:127) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.internal.schedulers.TrampolineScheduler$InnerCurrentThreadScheduler.enqueue(TrampolineScheduler.java:73) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.internal.schedulers.TrampolineScheduler$InnerCurrentThreadScheduler.schedule(TrampolineScheduler.java:52) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.internal.operators.OperatorRetryWithPredicate$SourceSubscriber.onNext(OperatorRetryWithPredicate.java:79) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.internal.operators.OperatorRetryWithPredicate$SourceSubscriber.onNext(OperatorRetryWithPredicate.java:45) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.internal.util.ScalarSynchronousObservable$WeakSingleProducer.request(ScalarSynchronousObservable.java:276) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.Subscriber.setProducer(Subscriber.java:209) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.internal.util.ScalarSynchronousObservable$JustOnSubscribe.call(ScalarSynchronousObservable.java:138) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.internal.util.ScalarSynchronousObservable$JustOnSubscribe.call(ScalarSynchronousObservable.java:129) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.Observable.subscribe(Observable.java:10423) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.Observable.subscribe(Observable.java:10390) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.observables.BlockingObservable.blockForSingle(BlockingObservable.java:443) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.observables.BlockingObservable.single(BlockingObservable.java:340) ~[rxjava-1.3.8.jar:1.3.8]
	at com.netflix.client.AbstractLoadBalancerAwareClient.executeWithLoadBalancer(AbstractLoadBalancerAwareClient.java:112) ~[ribbon-loadbalancer-2.2.5.jar:2.2.5]
	at org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient.execute(LoadBalancerFeignClient.java:63) ~[spring-cloud-openfeign-core-2.0.2.RELEASE.jar:2.0.2.RELEASE]
	at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:98) ~[feign-core-9.7.0.jar:na]
	... 67 common frames omitted

3.2 解决方案

  • 解决过程:
    (1) 设置超时时间
    在客户端配置如下:
feign:
  client:
    config:
      default:
        connectTimeout: 10000
        readTimeout: 10000
        loggerLevel: basic

效果: 发现设置的超时时间并没有生效
查看源码发现,feign 客户端默认连接和超时时间分别是10000ms,以及60000ms,且不会开启重试设置:
源码如下:

 //
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package feign;

import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;

public final class Request {
    private final String method;
    private final String url;
    private final Map<String, Collection<String>> headers;
    private final byte[] body;
    private final Charset charset;

    public static Request create(String method, String url, Map<String, Collection<String>> headers, byte[] body, Charset charset) {
        return new Request(method, url, headers, body, charset);
    }

    Request(String method, String url, Map<String, Collection<String>> headers, byte[] body, Charset charset) {
        this.method = (String)Util.checkNotNull(method, "method of %s", new Object[]{url});
        this.url = (String)Util.checkNotNull(url, "url", new Object[0]);
        this.headers = (Map)Util.checkNotNull(headers, "headers of %s %s", new Object[]{method, url});
        this.body = body;
        this.charset = charset;
    }

    public String method() {
        return this.method;
    }

    public String url() {
        return this.url;
    }

    public Map<String, Collection<String>> headers() {
        return this.headers;
    }

    public Charset charset() {
        return this.charset;
    }

    public byte[] body() {
        return this.body;
    }

    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append(this.method).append(' ').append(this.url).append(" HTTP/1.1\n");
        Iterator var2 = this.headers.keySet().iterator();

        while(var2.hasNext()) {
            String field = (String)var2.next();
            Iterator var4 = Util.valuesOrEmpty(this.headers, field).iterator();

            while(var4.hasNext()) {
                String value = (String)var4.next();
                builder.append(field).append(": ").append(value).append('\n');
            }
        }

        if (this.body != null) {
            builder.append('\n').append(this.charset != null ? new String(this.body, this.charset) : "Binary data");
        }

        return builder.toString();
    }

    public static class Options {
        private final int connectTimeoutMillis;
        private final int readTimeoutMillis;
        private final boolean followRedirects;

        public Options(int connectTimeoutMillis, int readTimeoutMillis, boolean followRedirects) {
            this.connectTimeoutMillis = connectTimeoutMillis;
            this.readTimeoutMillis = readTimeoutMillis;
            this.followRedirects = followRedirects;
        }

        public Options(int connectTimeoutMillis, int readTimeoutMillis) {
            this(connectTimeoutMillis, readTimeoutMillis, true);
        }
      //默认的连接超时时间和读超时时间
        public Options() {
            this(10000, 60000);
        }

        public int connectTimeoutMillis() {
            return this.connectTimeoutMillis;
        }

        public int readTimeoutMillis() {
            return this.readTimeoutMillis;
        }

        public boolean isFollowRedirects() {
            return this.followRedirects;
        }
    }
}

关于重试的源码如下,default 中显示为5次

 package feign;

import java.util.concurrent.TimeUnit;

public interface Retryer extends Cloneable {
    Retryer NEVER_RETRY = new Retryer() {
        public void continueOrPropagate(RetryableException e) {
            throw e;
        }

        public Retryer clone() {
            return this;
        }
    };

    void continueOrPropagate(RetryableException var1);

    Retryer clone();

    public static class Default implements Retryer {
        private final int maxAttempts;
        private final long period;
        private final long maxPeriod;
        int attempt;
        long sleptForMillis;

        public Default() {
            this(100L, TimeUnit.SECONDS.toMillis(1L), 5);
        }

        public Default(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) {
            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.5D, (double)(this.attempt - 1)));
            return interval > this.maxPeriod ? this.maxPeriod : interval;
        }

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

综上:最终解决方案只需在配置文件改为如下即可

@Configuration
public class FeignConfig implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        HttpServletRequest request = SessionUtils.getRequest();
        if (request != null) {
            requestTemplate.header("Authorization", SessionUtils.getRequest().getHeader("Authorization"));
        }
    }

    @Bean
    public Retryer feignRetryer() {
        return new Retryer.Default();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值