简介
为什么要用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;
(name = "user", fallback = UserFeignClientFallback.class)
public interface UserFeignClient {
("/user/{id}")
public User getUser( ("id") Long id);
}
UserFeignFallback
package com.example.feign;
import org.springframework.stereotype.Component;
public class UserFeignClientFallback implements UserFeignClient {
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;
("/product")
public class ProductController {
UserFeignClient userFeignService;
("/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;
("/user")
public class UserController {
("/{id}")
public User getUser( ("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;
(name = "user", fallbackFactory = UserFeignClientFallbackFactory.class)
public interface UserFeignClient {
("/user/{id}")
public User getUser( ("id") Long id);
}
UserFeignClientFallbackFactory
package com.example.product.feign;
import com.example.product.entity.User;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
public class UserFeignClientFallbackFactory implements FallbackFactory<UserFeignClient> {
public UserFeignClient create(Throwable throwable) {
System.out.println("fallback throwable: " + throwable);
System.out.println("fallback message: " + throwable.getMessage());
return new UserFeignClient() {
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;
("/product")
public class ProductController {
UserFeignClient userFeignService;
("/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;
("/user")
public class UserController {
("/{id}")
public User getUser( ("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
其他网址
3.Feign 服务异常不进入熔断
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;
public class KeepErrMsgConfiguration implements ErrorDecoder {
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;
public class NotBreakerConfiguration implements ErrorDecoder {
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;
}
}
配置方法
第一种:@Configuration
如上边所示,直接加此注解即可。
第二种:@EnableFeignClients
全局配置,@EnableFeignClients.defaultConfiguration注解。
此时,对于Decoder的配置类,就像上边那样,去掉@Configuration即可。
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;
(
defaultConfiguration = NotBreakerConfiguration.class
)
public class FeignApplication {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class, args);
}
}
第三种:@FeignClient
@FeignClient.configuration 注解。作用范围是Feign接口,优先级要高于上面两种。
此时,对于Decoder的配置类,就像上边那样,去掉@Configuration即可。
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;
(name = "user", url = "${user.url}",
decode404 = true,
fallbackFactory = UserFeignFactory.class,
configuration = NotBreakerConfiguration.class
)
public interface UserFeign {
void save(User user);
("/{id}")
User getUserByID( ("id") String id);
}
第四种
使用@Configuration
public class KeepErrMsgConfiguration{
public ErrorDecoder errorDecoder() {
return new MyErrorDecoder();
}
public static class MyErrorDecoder implements ErrorDecoder {
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外的异常");
}
}
}