Original link:www.ciphermagic.cn/spring-clou…
The problem
Recently, in the project development, I used Feign to call the service. When triggering the circuit breaker, I encountered the following problems:
- The exception information is as follows:
TestService#addRecord(ParamVO) failed and no fallback available.
; - Cannot get the original exception information thrown by the service provider;
- Implement some business methods without entering the circuit breaker, directly throw exceptions out;
The next steps will address each of these issues.
For failed and no fallback available. This abnormal message is because the project is on a circuit breaker:
feign.hystrix.enabled: true
Copy the code
The above exception is thrown when the service is called and the fallback method is not defined. This leads to the first solution.
@FeignClient
addfallback
Method and get exception information
There are two ways to add the fallback method to the @FeignClient-modified interface. To get exception information, use the fallbackFactory method:
@FeignClient(name = "serviceId", fallbackFactory = TestServiceFallback.class)
public interface TestService {
@RequestMapping(value = "/get/{id}", method = RequestMethod.GET)
Result get(@PathVariable("id") Integer id);
}
Copy the code
Specify fallbackFactory in the @FeignClient annotation, TestServiceFallback in the example above:
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 interface temporarily unavailable:";
@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) {
returnResultBuilder.unsuccess(ERR_MSG + msg); }}; }}Copy the code
By implementing FallbackFactory, you can get the exception thrown by the service in the create method. Note, however, that the exceptions here are Feign wrapped, and you can’t see the exceptions thrown by the original method directly in the exception message. At this time, the abnormal information is as follows:
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}
Copy the code
To clarify, in this example, the service provider’s interface return information will be encapsulated in a custom class Result, the content is the above content:
{"success":false."resultCode":null."message":"/ by zero"."model":null."models": []."pageInfo":null."timelineInfo":null."extra":null."validationMessages":null."valid":false}
Copy the code
Therefore, I want the exception message to be the content of message: / by zero, so that the exception can be easily identified when logging.
Retain the original exception information
When calling the service, if the service returns a status code other than 200, it goes into Feign’s ErrorDecoder, so if we want to parse the exception message, we need to override ErrorDecoder:
import feign.Response;
import feign.Util;
import feign.codec.ErrorDecoder;
/ * * *@Author: CipherCui
* @Description: Reserved feign service exception information *@Date: Created in 1:29 2018/6/2
*/
public class KeepErrMsgConfiguration {
@Bean
public ErrorDecoder errorDecoder(a) {
return new UserErrorDecoder();
}
/** * Custom error decoder */
public class UserErrorDecoder implements ErrorDecoder {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public Exception decode(String methodKey, Response response) {
Exception exception = null;
try {
// Get the original returned content
String json = Util.toString(response.body().asReader());
exception = new RuntimeException(json);
// Deserialize the returned content to Result, which should be modified according to your project
Result result = JsonMapper.nonEmptyMapper().fromJson(json, Result.class);
// A business exception throws a simple RuntimeException, preserving the original error message
if(! result.isSuccess()) { exception =newRuntimeException(result.getMessage()); }}catch (IOException ex) {
logger.error(ex.getMessage(), ex);
}
returnexception; }}}Copy the code
Here is an example that deserializes response.body() into a custom Result class, extracts the message from it, and throws a RuntimeException so that when it enters the fuse method, The exception we get is the RuntimeException we handled.
Note that the above examples are not generic, but the principles are common and should be adapted to suit your own projects.
To make the above code work, you also need to specify configuration in the @feignClient annotation:
@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);
}
Copy the code
Throw an exception without entering a circuit breaker
Sometimes we don’t want methods to go into circuit breaker logic, but just throw out the exception as it is. In this case, we only need to catch two points: do not enter the fuse, original.
As it is to get the original exception, has been introduced above, and not into the fuse, need to abnormal encapsulated into HystrixBadRequestException, for HystrixBadRequestException, Feign thrown directly, not into the fusing method.
So we just need to make a few changes to the KeepErrMsgConfiguration above:
/ * * *@Author: CipherCui
* @Description: Feign service is abnormal and does not enter fuse *@Date: Created in 1:29 2018/6/2
*/
public class NotBreakerConfiguration {
@Bean
public ErrorDecoder errorDecoder(a) {
return new UserErrorDecoder();
}
/** * Custom error decoder */
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);
/ / a business exception packaged into HystrixBadRequestException, don't enter the fusing logic
if(! result.isSuccess()) { exception =newHystrixBadRequestException(result.getMessage()); }}catch (IOException ex) {
logger.error(ex.getMessage(), ex);
}
returnexception; }}}Copy the code
conclusion
To achieve a better fusing effect, we should specify a fallback method for each interface. You can flexibly configure the KeepErrMsgConfiguration and NotBreakerConfiguration according to your service characteristics, or write your own Configuration.
Above example particularity is strong, inadequacy place please don’t hesitate to give advice. I hope you can get something useful from it and apply it to your own projects. Thank you for reading.
Original link:www.ciphermagic.cn/spring-clou…