timed-out and no fallback
這個(gè)錯(cuò)誤基本是出現(xiàn)在Hystrix熔斷器晕城,熔斷器的作用是判斷該服務(wù)能不能通,如果通了就不管了窖贤,調(diào)用在指定時(shí)間內(nèi)超時(shí)時(shí)砖顷,就會(huì)通過熔斷器進(jìn)行錯(cuò)誤返回。
一般設(shè)置如下配置的其中一個(gè)即可:
1主之、把時(shí)間設(shè)長
這里設(shè)置5秒
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
2择吊、把超時(shí)發(fā)生異常屬性關(guān)閉
hystrix.command.default.execution.timeout.enabled=false
3、禁用feign的hystrix
feign.hystrix.enabled: false
failed and no fallback available:
而通過上面設(shè)置只是針對(duì)熔斷器的錯(cuò)誤關(guān)閉槽奕,并不能解決根本問題几睛,比如Feign客戶端調(diào)用遠(yuǎn)程服務(wù)時(shí),默認(rèn)為8秒超時(shí)時(shí)間粤攒,如果在規(guī)定時(shí)間內(nèi)沒有返回所森,同樣會(huì)跳轉(zhuǎn)到熔斷器進(jìn)行處理。即使關(guān)閉了熔斷器的錯(cuò)誤夯接,但是總的錯(cuò)誤處理還會(huì)是有這個(gè)問題出現(xiàn)焕济。
那么要解決根本問題,就要從請(qǐng)求超時(shí)時(shí)間入手盔几,因?yàn)橛行┓?wù)可能存在調(diào)用時(shí)間長的問題晴弃,所以直接配置:
ribbon.ReadTimeout=60000
ribbon.ConnectTimeout=60000
這些才是真正解決請(qǐng)求超時(shí)的問題,如果不設(shè)置這個(gè)逊拍,被調(diào)用接口很慢時(shí)上鞠,會(huì)出現(xiàn)Read Timeout on Request。
而針對(duì)調(diào)用失敗重試的次數(shù)也可以設(shè)置:
ribbon.maxAutoRetries=0
failed and no fallback available
對(duì)于failed and no fallback available.這種異常信息芯丧,是因?yàn)轫?xiàng)目開啟了熔斷:
feign.hystrix.enabled: true
當(dāng)調(diào)用服務(wù)時(shí)拋出了異常芍阎,卻沒有定義fallback方法,就會(huì)拋出上述異常缨恒。由此引出了第一個(gè)解決方式谴咸。
@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接口暫時(shí)不可用: ";
@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);
}
};
}
}
通過實(shí)現(xiàn)FallbackFactory,可以在create方法中獲取到服務(wù)拋出的異常。但是請(qǐng)注意驼唱,這里的異常是被Feign封裝過的異常藻茂,不能直接在異常信息中看出原始方法拋出的異常。這時(shí)得到的異常信息形如:
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}
說明一下玫恳,本例子中辨赐,服務(wù)提供者的接口返回信息會(huì)統(tǒng)一封裝在自定義類Result中,內(nèi)容就是上述的content:
{"success":false,"resultCode":null,"message":"/ by zero","model":null,"models":[],"pageInfo":null,"timelineInfo":null,"extra":null,"validationMessages":null,"valid":false}
因此京办,異常信息我希望是message的內(nèi)容:/ by zero掀序,這樣打日志時(shí)能夠方便識(shí)別異常。
保留原始異常信息
當(dāng)調(diào)用服務(wù)時(shí)惭婿,如果服務(wù)返回的狀態(tài)碼不是200不恭,就會(huì)進(jìn)入到Feign的ErrorDecoder中,因此如果我們要解析異常信息财饥,就要重寫ErrorDecoder:
import feign.Response;
import feign.Util;
import feign.codec.ErrorDecoder;
/**
* @Author: CipherCui
* @Description: 保留 feign 服務(wù)異常信息
* @Date: Created in 1:29 2018/6/2
*/
public class KeepErrMsgConfiguration {
@Bean
public ErrorDecoder errorDecoder() {
return new UserErrorDecoder();
}
/**
* 自定義錯(cuò)誤
*/
public class UserErrorDecoder implements ErrorDecoder {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public Exception decode(String methodKey, Response response) {
Exception exception = null;
try {
// 獲取原始的返回內(nèi)容
String json = Util.toString(response.body().asReader());
exception = new RuntimeException(json);
// 將返回內(nèi)容反序列化為Result换吧,這里應(yīng)根據(jù)自身項(xiàng)目作修改
Result result = JsonMapper.nonEmptyMapper().fromJson(json, Result.class);
// 業(yè)務(wù)異常拋出簡單的 RuntimeException,保留原來錯(cuò)誤信息
if (!result.isSuccess()) {
exception = new RuntimeException(result.getMessage());
}
} catch (IOException ex) {
logger.error(ex.getMessage(), ex);
}
return exception;
}
}
}
上面是一個(gè)例子钥星,原理是根據(jù)response.body()反序列化為自定義的Result類沾瓦,提取出里面的message信息,然后拋出RuntimeException谦炒,這樣當(dāng)進(jìn)入到熔斷方法中時(shí)贯莺,獲取到的異常就是我們處理過的RuntimeException。
注意上面的例子并不是通用的宁改,但原理是相通的缕探,大家要結(jié)合自身的項(xiàng)目作相應(yīng)的修改。
要使上面代碼發(fā)揮作用还蹲,還需要在@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);
}
不進(jìn)入熔斷爹耗,直接拋出異常
有時(shí)我們并不希望方法進(jìn)入熔斷邏輯,只是把異常原樣往外拋谜喊。這種情況我們只需要捉住兩個(gè)點(diǎn):不進(jìn)入熔斷潭兽、原樣。
原樣就是獲取原始的異常锅论,上面已經(jīng)介紹過了,而不進(jìn)入熔斷楣号,需要把異常封裝成HystrixBadRequestException最易,對(duì)于HystrixBadRequestException怒坯,F(xiàn)eign會(huì)直接拋出,不進(jìn)入熔斷方法藻懒。
因此我們只需要在上述KeepErrMsgConfiguration的基礎(chǔ)上作一點(diǎn)修改即可:
/**
* @Author: CipherCui
* @Description: feign 服務(wù)異常不進(jìn)入熔斷
* @Date: Created in 1:29 2018/6/2
*/
public class NotBreakerConfiguration {
@Bean
public ErrorDecoder errorDecoder() {
return new UserErrorDecoder();
}
/**
* 自定義錯(cuò)誤
*/
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);
// 業(yè)務(wù)異常包裝成 HystrixBadRequestException剔猿,不進(jìn)入熔斷邏輯
if (!result.isSuccess()) {
exception = new HystrixBadRequestException(result.getMessage());
}
} catch (IOException ex) {
logger.error(ex.getMessage(), ex);
}
return exception;
}
}
}
總結(jié)
為了更好的達(dá)到熔斷效果,我們應(yīng)該為每個(gè)接口指定fallback方法嬉荆。而根據(jù)自身的業(yè)務(wù)特點(diǎn)归敬,可以靈活的配置上述的KeepErrMsgConfiguration和NotBreakerConfiguration,或自己編寫Configuration鄙早。
參考:http://www.ciphermagic.cn/spring-cloud-feign-hystrix.html