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();
}
}