所有分类
  • 所有分类
  • 未分类

Spring Cloud Feign–Fallback的用法

简介

为什么要用fallback

Feign默认认为非2XX都是异常。对于404这种非常容易抛出的业务异常来说,没两下就circuit break(断路)了。

可降级设计中,希望调用某个服务失败的时候能够让流程走下去而非抛异常,但是又不希望每个调用的地方加try catch,这个时候可以用Feign的Fallback。

但是,这种情况很少见。一般直接全局异常处理的。

hystrix配置

fallback是hystrix的功能,所以必须开启hystrix(默认是关闭的)。

共存问题

 fallback比fallbackfactory优先级高。若都存在,会走fallback调用。

防止hystrix直接调用fallback

有时候可能feign直接到了fallback中,原因如下:

Hystrix默认的超时时间是1秒,如果超过这个时间尚未响应,将会进入fallback代码。而首次请求往往会比较慢(由于Ribbon是懒加载的,在首次请求时,才会开始初始化相关类),这个响应时间可能就大于1秒了。

解决方法

法1:Ribbon配置饥饿加载(最佳推荐) 

饥饿加载:即在启动的时候便加载所有配置项的应用程序上下文。

ribbon:
  # 饥饿加载
  eager-load:
    # 是否开启饥饿加载
    enabled: true
    # 饥饿加载的服务
    clients: demo-goods,demo-product

法2:调用端配置

eureka.client.fetch-registy: true

Fallback

说明

Fallback只能覆写方法,但无法捕获异常(获取不到HTTP请求错误状态码和信息)。

HystrixTargeter.targetWithFallback方法实现了@FeignClient.fallback处理逻辑,通过源码可以知道UserFeignFallback回调类是从Spring容器中获取的,所以UserFeignFallback由spring创建。

UserFeignClient

package com.example.product.feign;

import com.example.product.entity.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(name = "user", fallback = UserFeignClientFallback.class)
public interface UserFeignClient {
    @GetMapping("/user/{id}")
    public User getUser(@PathVariable("id") Long id);
}

UserFeignFallback

package com.example.feign;

import org.springframework.stereotype.Component;

@Component
public class UserFeignClientFallback implements UserFeignClient {

    @Override
    public User getUserByID(String id) {
        User user = new User();
        user.setId(-1);
        return user;
    }
}

ProductController

package com.example.product.controller;

import com.example.product.entity.User;
import com.example.product.feign.UserFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/product")
public class ProductController {
    @Autowired
    UserFeignClient userFeignService;

    @GetMapping("/feign")
    public User testFeign(){
        User user = userFeignService.getUser(1L);;
        return user;
    }
}

UserController

package com.example.user.controller;

import com.example.user.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.logging.Logger;

@RestController
@RequestMapping("/user")
public class UserController {
    @GetMapping("/{id}")
    public User getUser(@PathVariable("id") Long id) {
        User user = new User();
        user.setId(id);
        user.setUserName("user_name_" + id);
        user.setAge(20);
        return user;
    }
}

postman访问: http://localhost:9001/product/feign

正常结果:

{
    "id": 1,
    "userName": "user_name_1",
    "age": 20
}

将User服务直接关掉:结果

 postman结果:

{
    "id": -1,
    "userName": null,
    "age": null
}

FallbackFactory

简介

上面的实现方式简单,但无法捕获异常(获取不到HTTP请求错误状态码和信息) ,这时可用工厂模式来实现Fallback

UserFeignClient

package com.example.product.feign;

import com.example.product.entity.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(name = "user", fallbackFactory = UserFeignClientFallbackFactory.class)
public interface UserFeignClient {
    @GetMapping("/user/{id}")
    public User getUser(@PathVariable("id") Long id);
}

UserFeignClientFallbackFactory 

package com.example.product.feign;

import com.example.product.entity.User;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;

@Component
public class UserFeignClientFallbackFactory implements FallbackFactory<UserFeignClient> {
    @Override
    public UserFeignClient create(Throwable throwable) {
        System.out.println("fallback throwable: " + throwable);
        System.out.println("fallback message: " + throwable.getMessage());

        return new UserFeignClient() {
            @Override
            public User getUser(Long id) {
                User user = new User();
                user.setId(-1L);
                return user;
            }
        };
    }
}

ProductController

package com.example.product.controller;

import com.example.product.entity.User;
import com.example.product.feign.UserFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/product")
public class ProductController {
    @Autowired
    UserFeignClient userFeignService;

    @GetMapping("/feign")
    public User testFeign(){
        User user = userFeignService.getUser(1L);;
        return user;
    }
}

UserController

package com.example.user.controller;

import com.example.user.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.logging.Logger;

@RestController
@RequestMapping("/user")
public class UserController {
    @GetMapping("/{id}")
    public User getUser(@PathVariable("id") Long id) {
        User user = new User();
        user.setId(id);
        user.setUserName("user_name_" + id);
        user.setAge(20);
        return user;
    }
}

postman访问: http://localhost:9001/product/feign

正常结果:

{
    "id": 1,
    "userName": "user_name_1",
    "age": 20
}

将User服务直接关掉:结果

 postman结果:

{
    "id": -1,
    "userName": null,
    "age": null
}

后端打印

fallback throwable: java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: user
fallback message: com.netflix.client.ClientException: Load balancer does not have available server for client: user

ErrorDecoder接口处理请求错误信息,ErrorDecoder.Default这个默认实现抛出FeignException异常。

FeignException.status 方法返回HTTP状态码,FallbackFactory.create默认情况下可以强制转换成FeignException异常这样就可以获取到HTTP状态码了。

自定义ErrorDecoder

ErrorDecoder与fallback的执行顺序:先走ErrorDecoder拦截器,再走熔断的fallback。

@FeignClient加上decode404 = true这一个参数,Feign对于2XX和404 ,都不会走Fallback了。

排除404,已经基本上够用了,如果想把409、400等status也加到例外中,可以重写一下Feign的errorDecoder。

方案1.解析错误

package com.example.product.config;

import feign.Response;
import feign.Util;
import feign.codec.ErrorDecoder;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;

import java.io.IOException;

@Component
public class KeepErrMsgConfiguration implements ErrorDecoder {
    @Override
    public Exception decode(String methodKey, Response response) {
        System.out.println(response.status());
        if (response.status() != HttpStatus.OK.value()) {
            try {
                // 原始的返回内容
                String json = Util.toString(response.body().asReader());
                System.out.println(json);
                return new RuntimeException("try中的异常");
            } catch (IOException e) {
                return new RuntimeException("cat中的异常");
            }
        }
        return new RuntimeException("if外的异常");
    }
}

方案2. 不熔断,把异常原样往外抛。

package com.example.product.config;

import com.netflix.hystrix.exception.HystrixBadRequestException;
import feign.Response;
import feign.Util;
import feign.codec.ErrorDecoder;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;

import java.io.IOException;

@Component
public class NotBreakerConfiguration implements ErrorDecoder {
    @Override
    public Exception decode(String methodKey, Response response) {
        System.out.println(response.status());
        Exception exception = null;
        if (response.status() != HttpStatus.OK.value()) {
            try {
                // 获取原始的返回内容(来自GlobalExceptionHandler类中的返回信息)
                String json = Util.toString(response.body().asReader());
                System.out.println(json);
                exception = new HystrixBadRequestException("if中的");
            } catch (IOException e) {
                exception = new HystrixBadRequestException(e.getMessage());
            }
        }
        return exception;
    }
}

配置方法

配置方法1:@Component

如下边所示,注入到容器即可。

package com.example.product.config;

import com.netflix.hystrix.exception.HystrixBadRequestException;
import feign.Response;
import feign.Util;
import feign.codec.ErrorDecoder;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;

import java.io.IOException;

@Component
public class NotBreakerConfiguration implements ErrorDecoder {
    @Override
    public Exception decode(String methodKey, Response response) {
        System.out.println(response.status());
        Exception exception = null;
        if (response.status() != HttpStatus.OK.value()) {
            try {
                // 获取原始的返回内容(来自GlobalExceptionHandler类中的返回信息)
                String json = Util.toString(response.body().asReader());
                System.out.println(json);
                exception = new HystrixBadRequestException("if中的");
            } catch (IOException e) {
                exception = new HystrixBadRequestException(e.getMessage());
            }
        }
        return exception;
    }
}

配置方法2:@EnableFeignClients

全局配置@EnableFeignClients注解。
(NotBreakerConfiguration要去掉@Component)。

package com.example;

import com.example.feign.FeignClientsConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.feign.EnableFeignClients;

@EnableFeignClients(
        defaultConfiguration = NotBreakerConfiguration.class
)
@SpringBootApplication
public class FeignApplication {
    public static void main(String[] args) {
        SpringApplication.run(FeignApplication.class, args);
    }
}

配置方法3:@FeignClient

@FeignClient注解。作用范围是Feign接口,优先级要高于上面两种。
(NotBreakerConfiguration要去掉@Component)。

package com.example.feign;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.List;

@FeignClient(name = "user", url = "${user.url}",
        decode404 = true,
        fallbackFactory = UserFeignFactory.class,
        configuration = NotBreakerConfiguration.class
)

public interface UserFeign {
    @PostMapping
    void save(User user);

    @GetMapping("/{id}")
    User getUserByID(@PathVariable("id") String id);
}
0

评论0

请先

显示验证码
没有账号?注册  忘记密码?

社交账号快速登录