Spring Cloud Feign 熔断、异常处理

Spring Cloud Alibaba 用Feign调取服务时,如果服务端发生异常要传递给客户端,或者客户端拦截到异常响应做相应的业务处理,可有几种实现。此例子源码请看GitHub

一、默认熔断策略

我使用的熔断组件是sentinel,其他的熔断组件一样。Feign都是支持的;

  1. 开启熔断开关
feign:
  sentinel:
    enabled: true
  1. 配置熔断策略,也就是在@FeignClient客户端接口中配置 fallback 参数;
@Component
@FeignClient(name = ServiceConstants.FEIGN_SERVICE,fallback = FeignServiceFallback.class,configuration = FeignConfig.class)
public interface FeignService {

    @GetMapping(value = ServiceConstants.FEIGN_SERVICE_API_TEST)
    TestObject getTestObject();

}
@Slf4j
@Component
public class FeignServiceFallback implements FeignService {


    @Override
    public TestObject getTestObject() {
        log.error("服务调用接口异常");
        return null;
    }
}

FeignServiceFallback 类就是服务不可用时的返回策略。当然还有一个fallbackFactory 后边会说。

这有个问题,只有服务不可用时才会发生这个熔断,异常是不走这的。

二、异常处理

  1. Feign获取异常是实现一个ErrorDecoder接口来的,相当于可以自定义异常吧。
public class FeignException implements ErrorDecoder {
    @Override
    public Exception decode(String s, Response response) {
        System.out.println("访问结果码:"+response.status());
        return null;
    }
}

配置类实例化;@FeignClient(configuration = FeignConfig.class) 上边有

@Component
public class FeignConfig {
	@Bean
    public ErrorDecoder errorDecoder(){
        return new FeignException();
    }
}

实现这个接口只能获取到非200状态的异常返回。正常请求是获取不到的。比如在服务端返回自定义status码的异常,这获取不到。只能获取到4xx、5xx。(http 接口项目,一般都会自定义返回结构和状态码。Http的状态码还是200。如果需要传递业务异常那需要另外的办法。)

三、业务异常捕获(1)

Feign定义的异常有几种基本都是继承的FeignException,Feign因为使用HttpClient或okHttp 调用服务接口,在Spring Mvc 中直接用全局异常catch不到。(谁有办法告知一下)所以只能在返回数据处做一下处理。
Feign提供了一个ResponseEntityDecoder类,对数据做编码处理,可以用这个类拦截一下返回数据。

public class FeignConfig {
	@Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;
    
	@Bean
    public Decoder feignDecoder(){
        return new AwardResponseEntityDecoder(new SpringDecoder(this.messageConverters));
    }
}
public class AwardResponseEntityDecoder extends ResponseEntityDecoder {

    public AwardResponseEntityDecoder(Decoder decoder) {
        super(decoder);
    }

    @Override
    public Object decode(Response response, Type type) throws IOException,FeignException {
        if (response.body() == null) {
            throw new DecodeException(response.status(), "返回数据为空", response.request());
        }
        String bodyStr = Util.toString(response.body().asReader(Util.UTF_8));
        //对结果进行转换
        ResultBody resultBody=JSON.parseObject(bodyStr,ResultBody.class);
        //此处如果不是正常的200状态,则抛出异常,异常就会进入熔断,走熔断策略
        if (resultBody.getCode() != 200) {
            throw new DecodeException(resultBody.code, resultBody.message, response.request());
        }
        return jsonToobj(bodyStr,type);
    }


    /**
     * 反序列化
     * @param jsonStr
     * @param targetType
     * @param <T>
     * @return
     */
    public static <T> T jsonToobj(String jsonStr, Type targetType) {
        try {
            JavaType javaType = TypeFactory.defaultInstance().constructType(targetType);
            ObjectMapper mapper = new ObjectMapper();
            return mapper.readValue(jsonStr, javaType);
        } catch (IOException e) {
            throw new IllegalArgumentException("Feign JSON转换为对象异常:" + jsonStr, e);
        }
    }
}

服务端发生异常有统一返回包装类,如下;如果没有异常则返回的是一个正常的对象(本例为TestObject )。所以在上边会先转化为ResultBody 对象,如果code有值则发生了异常。

public class ResultBody implements Serializable {

    
    public boolean success;

    public int code;

    public String message;

    public Object data;

    public Long timestamp = System.currentTimeMillis();

    ///省略.....
    
}

这样根据返回内容抛出DecodeException会直接进入熔断处理器。二中的 FeignServiceFallback
我的客户端也统一返回ResultBody所以返回结果是这样的:

{
    "success": true,
    "code": 200,
    "message": "",
    "data": null,
    "timestamp": 1600155600278
}

data中的null就是FeignServiceFallback返回的内容。可以返回对应对象。

进一步处理业务异常问题,当前只是知道了服务端发生了异常,并不知道具体发生了什么。我想直接返回服务端异常内容。

四、业务异常捕获(2)

把(三) 的内容改造一下即可直接返回服务端异常。
fallback 改为 fallbackFactory

@Component
@FeignClient(name = ServiceConstants.FEIGN_SERVICE,fallbackFactory = GeneralFallbackFactory.class,configuration = FeignConfig.class)
public interface FeignService {

    @GetMapping(value = ServiceConstants.FEIGN_SERVICE_API_TEST)
    TestObject getTestObject();

}
/**
 * FeignClient 设置fallbackFactory 返回统一格式异常  与fallback不能共用
 * ServiceException 是自定义的异常类,ErrorCode.SERVICE_ERROR是自定义错误码,请自行创建。 
 */
@Component
public class GeneralFallbackFactory implements FallbackFactory {


    @Override
    public Object create(Throwable throwable) {
        throw new ServiceException(throwable.getMessage(), ErrorCode.SERVICE_ERROR);
    }
}

全局异常处理异常ServiceException

	@ExceptionHandler(value = ServiceException.class)
    public ResultBody handleServiceException(ServiceException se) {
        logger.error("ServiceException:{}", se.getMessage());
        return new ResultBody(se.getErrorCode().code, se.getMessage());
    }

FallbackFactory 也是熔断处理器,其中可以获取到异常信息,所以使用FallbackFactory 客户端可以看到异常信息,抛出异常捕获。当然FallbackFactory还有其他用法请根据需求自行处理。
现在客户端调用获得的结果可以直接显示服务端异常:

{
    "success": false,
    "code": 2000,
    "message": "未登录",
    "data": null,
    "timestamp": 1600156631671
}
  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值