timed-out and no fallback
這個錯誤基本是出現在Hystrix熔斷器,熔斷器的作用是判斷該服務能不能通,如果通了就不管了,調用在指定時間內超時時,就會通過熔斷器進行錯誤返回。
一般設置如下配置的其中一個即可:
1、把時間設長
這里設置5秒
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
2、把超時發生異常屬性關閉
hystrix.command.default.execution.timeout.enabled=false
3、禁用feign的hystrix
feign.hystrix.enabled: false
failed and no fallback available:
而通過上面設置只是針對熔斷器的錯誤關閉,並不能解決根本問題,比如Feign客戶端調用遠程服務時,默認為8秒超時時間,如果在規定時間內沒有返回,同樣會跳轉到熔斷器進行處理。即使關閉了熔斷器的錯誤,但是總的錯誤處理還會是有這個問題出現。
那么要解決根本問題,就要從請求超時時間入手,因為有些服務可能存在調用時間長的問題,所以直接配置:
ribbon.ReadTimeout=60000
ribbon.ConnectTimeout=60000
這些才是真正解決請求超時的問題,如果不設置這個,被調用接口很慢時,會出現Read Timeout on Request。
而針對調用失敗重試的次數也可以設置:
ribbon.maxAutoRetries=0
failed and no fallback available
對於failed and no fallback available.這種異常信息,是因為項目開啟了熔斷:
feign.hystrix.enabled: true
當調用服務時拋出了異常,卻沒有定義fallback方法,就會拋出上述異常。由此引出了第一個解決方式。
@FeignClient加上fallback方法,並獲取異常信息
為@FeignClient修飾的接口加上fallback方法有兩種方式,由於要獲取異常信息,所以使用fallbackFactory的方式:
@FeignClient(name = "serviceId", fallbackFactory = TestServiceFallback.class)
public interface TestService {
@RequestMapping(value = "/get/{id}", method = RequestMethod.GET)
Result get(@PathVariable("id") Integer id);
}
在@FeignClient注解中指定fallbackFactory,上面例子中是TestServiceFallback:
import feign.hystrix.FallbackFactory;
import org.apache.commons.lang3.StringUtils;
@Component
public class TestServiceFallback implements FallbackFactory<TestService> {
private static final Logger LOG = LoggerFactory.getLogger(TestServiceFallback.class);
public static final String ERR_MSG = "Test接口暫時不可用: ";
@Override
public TestService create(Throwable throwable) {
String msg = throwable == null ? "" : throwable.getMessage();
if (!StringUtils.isEmpty(msg)) {
LOG.error(msg);
}
return new TestService() {
@Override
public String get(Integer id) {
return ResultBuilder.unsuccess(ERR_MSG + msg);
}
};
}
}
通過實現FallbackFactory,可以在create方法中獲取到服務拋出的異常。但是請注意,這里的異常是被Feign封裝過的異常,不能直接在異常信息中看出原始方法拋出的異常。這時得到的異常信息形如:
status 500 reading TestService#addRecord(ParamVO); content:
{"success":false,"resultCode":null,"message":"/ by zero","model":null,"models":[],"pageInfo":null,"timelineInfo":null,"extra":null,"validationMessages":null,"valid":false}
說明一下,本例子中,服務提供者的接口返回信息會統一封裝在自定義類Result中,內容就是上述的content:
{"success":false,"resultCode":null,"message":"/ by zero","model":null,"models":[],"pageInfo":null,"timelineInfo":null,"extra":null,"validationMessages":null,"valid":false}
因此,異常信息我希望是message的內容:/ by zero,這樣打日志時能夠方便識別異常。
保留原始異常信息
當調用服務時,如果服務返回的狀態碼不是200,就會進入到Feign的ErrorDecoder中,因此如果我們要解析異常信息,就要重寫ErrorDecoder:
import feign.Response;
import feign.Util;
import feign.codec.ErrorDecoder;
/**
* @Author: CipherCui
* @Description: 保留 feign 服務異常信息
* @Date: Created in 1:29 2018/6/2
*/
public class KeepErrMsgConfiguration {
@Bean
public ErrorDecoder errorDecoder() {
return new UserErrorDecoder();
}
/**
* 自定義錯誤
*/
public class UserErrorDecoder implements ErrorDecoder {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public Exception decode(String methodKey, Response response) {
Exception exception = null;
try {
// 獲取原始的返回內容
String json = Util.toString(response.body().asReader());
exception = new RuntimeException(json);
// 將返回內容反序列化為Result,這里應根據自身項目作修改
Result result = JsonMapper.nonEmptyMapper().fromJson(json, Result.class);
// 業務異常拋出簡單的 RuntimeException,保留原來錯誤信息
if (!result.isSuccess()) {
exception = new RuntimeException(result.getMessage());
}
} catch (IOException ex) {
logger.error(ex.getMessage(), ex);
}
return exception;
}
}
}
上面是一個例子,原理是根據response.body()反序列化為自定義的Result類,提取出里面的message信息,然后拋出RuntimeException,這樣當進入到熔斷方法中時,獲取到的異常就是我們處理過的RuntimeException。
注意上面的例子並不是通用的,但原理是相通的,大家要結合自身的項目作相應的修改。
要使上面代碼發揮作用,還需要在@FeignClient注解中指定configuration:
@FeignClient(name = "serviceId", fallbackFactory = TestServiceFallback.class, configuration = {KeepErrMsgConfiguration.class})
public interface TestService {
@RequestMapping(value = "/get/{id}", method = RequestMethod.GET)
String get(@PathVariable("id") Integer id);
}
不進入熔斷,直接拋出異常
有時我們並不希望方法進入熔斷邏輯,只是把異常原樣往外拋。這種情況我們只需要捉住兩個點:不進入熔斷、原樣。
原樣就是獲取原始的異常,上面已經介紹過了,而不進入熔斷,需要把異常封裝成HystrixBadRequestException,對於HystrixBadRequestException,Feign會直接拋出,不進入熔斷方法。
因此我們只需要在上述KeepErrMsgConfiguration的基礎上作一點修改即可:
/**
* @Author: CipherCui
* @Description: feign 服務異常不進入熔斷
* @Date: Created in 1:29 2018/6/2
*/
public class NotBreakerConfiguration {
@Bean
public ErrorDecoder errorDecoder() {
return new UserErrorDecoder();
}
/**
* 自定義錯誤
*/
public class UserErrorDecoder implements ErrorDecoder {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public Exception decode(String methodKey, Response response) {
Exception exception = null;
try {
String json = Util.toString(response.body().asReader());
exception = new RuntimeException(json);
Result result = JsonMapper.nonEmptyMapper().fromJson(json, Result.class);
// 業務異常包裝成 HystrixBadRequestException,不進入熔斷邏輯
if (!result.isSuccess()) {
exception = new HystrixBadRequestException(result.getMessage());
}
} catch (IOException ex) {
logger.error(ex.getMessage(), ex);
}
return exception;
}
}
}
總結
為了更好的達到熔斷效果,我們應該為每個接口指定fallback方法。而根據自身的業務特點,可以靈活的配置上述的KeepErrMsgConfiguration和NotBreakerConfiguration,或自己編寫Configuration。
參考:http://www.ciphermagic.cn/spring-cloud-feign-hystrix.html