0
点赞
收藏
分享

微信扫一扫

Feign--Fallback

文风起武 2022-03-23 阅读 25



简介

为什么要用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

其他网址

​​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;

@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;
}
}

配置方法

第一种:@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;

@EnableFeignClients(
defaultConfiguration = NotBreakerConfiguration.class
)

@SpringBootApplication
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;

@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);
}

第四种

使用@Configuration

@Configuration
public class KeepErrMsgConfiguration{
@Bean
public ErrorDecoder errorDecoder() {
return new MyErrorDecoder();
}

public static class MyErrorDecoder 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外的异常");
}
}
}


举报

相关推荐

0 条评论