深入浅出Spring Cloud整合openfeign实现RPC

前言
Spring Cloud OpenFeign 3.0.4

Spring Cloud 版本 2020.0.1

案例说明
在这里插入图片描述
8951是消费者,服务消费方,调用方
8952-user 是生产者,服务提供方,被调用方

服务提供者,被调用方 8952-user

完整依赖如下

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>

        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>

        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <scope>providedscope>
        dependency>
    dependencies>

作为被调用方需要将自己注册到注册中心,这里以nacos为例。项目结构图如下所示。

深入浅出Spring Cloud整合openfeign实现RPC_第1张图片

bootstrap.yml

spring:
  application:
    name: openfeign-8952-user
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
server:
  port: 8952

UserController 随便写几个测试方法

@RestController
public class UserController {

    private final static Logger logger = LoggerFactory.getLogger(UserController.class);

    @Value(value = "${server.port}")
    private String port;

    @GetMapping("user")

    public String getUser() {
        return "user" + port;
    }

    @GetMapping("user2")
    public String getUser2(Integer id) {
        return "this is user2:" + id + port;
    }

    @PostMapping("user3")
    public String getUser3(@RequestBody Integer id) {
        return "this is user3:" + id + port;
    }

    @PostMapping("user4")
    public String getUser4(@RequestBody Integer id, HttpServletRequest request) {
        logger.info("header {}", request.getHeader("token"));
        return "this is user3:" + id + port;
    }

    @PostMapping("user5")
    public String getUser5() {
        try {
            TimeUnit.SECONDS.sleep(11);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "ok...";

    }
}

作为生产者,和任何其他单体应用的区别是把自己注册到注册中心而已。

服务消费者,调用方 8951

完整依赖如下

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>

        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-loadbalancerartifactId>
        dependency>
        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <scope>providedscope>
        dependency>

    dependencies>

完整结构图
深入浅出Spring Cloud整合openfeign实现RPC_第2张图片
UserClient

@FeignClient("openfeign-8952-user")
public interface UserClient {

    @GetMapping("user")
    String getUser();

    @GetMapping("user2")
    String getUser2(@RequestParam(value = "id") Integer id);

    @PostMapping(value = "user3", headers = "{}")
    String getUser3(@RequestBody Integer id);

    @PostMapping("user4")
    String getUser4(@RequestBody Integer id, @RequestHeader String token);

    @PostMapping("user5")
    String getUser5();

}

UserController

@RestController
public class UserController {

    @Autowired
    UserClient userClient;

    @GetMapping("user")
    public String getUser() {
        return userClient.getUser();
    }

    @GetMapping("user2")
    public String getUser2(Integer id) {
        return userClient.getUser2(id);
    }

    @GetMapping("user3")
    public String getUser3(Integer id) {
        return userClient.getUser3(id);
    }

    @GetMapping("user4")
    public String getUser4(Integer id) {
        String token = UUID.randomUUID().toString();
        return userClient.getUser4(id, token);
    }

    @GetMapping("user5")
    public String getUser5() {
        return userClient.getUser5();
    }
}

nacos服务列表如下
深入浅出Spring Cloud整合openfeign实现RPC_第3张图片
访问 http://localhost:8951/user 成功返回 user8952

注意事项

在比较新的版本(如3.0.4)中如果没有添加 spring-cloud-starter-loadbalancer 会报错,如下图所示。

在这里插入图片描述
后面我们会分析报错的一个原理。

负载均衡

快速启动另一台服务,右键选择复制配置
深入浅出Spring Cloud整合openfeign实现RPC_第4张图片
在程序参数指定另一个端口

--server.port=8953

深入浅出Spring Cloud整合openfeign实现RPC_第5张图片
启动服务并查看注册中心
深入浅出Spring Cloud整合openfeign实现RPC_第6张图片

深入浅出Spring Cloud整合openfeign实现RPC_第7张图片
访问 8951/user
深入浅出Spring Cloud整合openfeign实现RPC_第8张图片
可以看到已经具有了负载均衡的能力。

loadbalancer 依赖与启动分析

我们删除 spring-cloud-starter-loadbalancer 依赖根据报错信息找到对应的方法。
在这里插入图片描述
从上面截图第三行这个loadBalance方法点进去,源码如下所示

	protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
		Client client = getOptional(context, Client.class);
		if (client != null) {
			builder.client(client);
			Targeter targeter = get(context, Targeter.class);
			return targeter.target(this, builder, context, target);
		}
		//如果 client == null
		throw new IllegalStateException(
				"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?");
	}

可以看到 throw 后面的内容就是报错信息里面的提示。也就是说client == null的时候会抛出这个异常。

那么 Client client = getOptional(context, Client.class); 这一行就是重点。点进去看一下实现。

	protected <T> T getOptional(FeignContext context, Class<T> type) {
		return context.getInstance(contextId, type);
	}

调用了context.getInstance继续点进去。

	public <T> T getInstance(String name, Class<T> type) {
		AnnotationConfigApplicationContext context = getContext(name);
		try {
			return context.getBean(type);
		}
		catch (NoSuchBeanDefinitionException e) {
			// ignore
		}
		return null;
	}

这里是从容器AnnotationConfigApplicationContext中获取到bean,那么谁创建的这个bean呢?

一般来说都是由AutoConfiguration默认配置,所以我们找到loadbalancer包下的AutoConfiguration类。

深入浅出Spring Cloud整合openfeign实现RPC_第9张图片

FeignLoadBalancerAutoConfiguration 部分源码如下所示。

@ConditionalOnClass(Feign.class)
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
@AutoConfigureBefore(FeignAutoConfiguration.class)
@AutoConfigureAfter({ BlockingLoadBalancerClientAutoConfiguration.class, LoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties(FeignHttpClientProperties.class)
@Configuration(proxyBeanMethods = false)
@Import({ HttpClientFeignLoadBalancerConfiguration.class, OkHttpFeignLoadBalancerConfiguration.class,
		DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {}

通过import将三个客户端的配置类加入容器,由于我们还未做客户端切换所以找到Default。

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(LoadBalancerProperties.class)
class DefaultFeignLoadBalancerConfiguration {

	@Bean
	@ConditionalOnMissingBean
	@Conditional(OnRetryNotEnabledCondition.class)
	public Client feignClient(LoadBalancerClient loadBalancerClient, LoadBalancerProperties properties,
			LoadBalancerClientFactory loadBalancerClientFactory) {
		return new FeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient, properties,
				loadBalancerClientFactory);
	}

	@Bean
	@ConditionalOnMissingBean
	@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
	@ConditionalOnBean(LoadBalancedRetryFactory.class)
	@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
			matchIfMissing = true)
	public Client feignRetryClient(LoadBalancerClient loadBalancerClient,
			LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties,
			LoadBalancerClientFactory loadBalancerClientFactory) {
		return new RetryableFeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient,
				loadBalancedRetryFactory, properties, loadBalancerClientFactory);
	}

}

所以容器中获取到的Client类型的bean就是FeignBlockingLoadBalancerClient。如何验证?

我们回到loadBalance这个方法打一个断点看一下获取到的client类型就可以了。

深入浅出Spring Cloud整合openfeign实现RPC_第10张图片
删掉依赖后查看 FeignLoadBalancerAutoConfiguration 类情况
深入浅出Spring Cloud整合openfeign实现RPC_第11张图片
在ConditionalOnBean的作用下,这个AutoConf 不会执行就会导致容器中没有对应的 Client 。

到此为止,依赖不添加为什么报错已经说清楚了,那么获取到客户端负载均衡的过程呢?

负载均衡原理

在上面我们已经获取到了这么一个客户端 FeignBlockingLoadBalancerClient。里面有一个execute方法。

	@Override
	public Response execute(Request request, Request.Options options) throws IOException {}

猜想:在请求接口的时候会进到这里面。

验证猜想:在 loadBalancerClient.choose(serviceId, lbRequest); 这一行打一个断点观察输出

方法调用栈过程如下图
深入浅出Spring Cloud整合openfeign实现RPC_第12张图片
经过了动态代理的invoke,最终进到上面刚刚获取的FeignBlockingLoadBalancerClient的execute里面。

		ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);

最终通过这一行来筛选出负载均衡后的服务,我们重点看一下里面的实现。

点进去来到 BlockingLoadBalancerClient类中的choose方法。简称为阻塞的负载均衡客户端

	@Override
	public <T> ServiceInstance choose(String serviceId, Request<T> request) {
		ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId);
		if (loadBalancer == null) {
			return null;
		}
		Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();
		if (loadBalancerResponse == null) {
			return null;
		}
		return loadBalancerResponse.getServer();
	}

这个类是由 BlockingLoadBalancerClientAutoConfiguration 这个类装配而来。

@Configuration(proxyBeanMethods = false)
@LoadBalancerClients
@AutoConfigureAfter(LoadBalancerAutoConfiguration.class)
@AutoConfigureBefore({ org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration.class,
		AsyncLoadBalancerAutoConfiguration.class })
@ConditionalOnClass(RestTemplate.class)
public class BlockingLoadBalancerClientAutoConfiguration {

	@Bean
	@ConditionalOnBean(LoadBalancerClientFactory.class)
	@ConditionalOnMissingBean
	public LoadBalancerClient blockingLoadBalancerClient(LoadBalancerClientFactory loadBalancerClientFactory,
			LoadBalancerProperties properties) {
		return new BlockingLoadBalancerClient(loadBalancerClientFactory, properties);
	}
	... }

choose方法主要分为两段,第一段获得 ReactiveLoadBalancer 这么一个类型的变量。
第二段获得 Response 这么一个类型的返回值,里面包含了服务器相关信息。

Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();
loadBalancer.choose(request)

在这里插入图片描述
深入浅出Spring Cloud整合openfeign实现RPC_第13张图片

@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
public class LoadBalancerClientConfiguration {

	private static final int REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER = 193827465;

	@Bean
	@ConditionalOnMissingBean
	public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
			LoadBalancerClientFactory loadBalancerClientFactory) {
		String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
		return new RoundRobinLoadBalancer(
				loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
	}

RoundRobinLoadBalancer 中核心方法

	public Mono<Response<ServiceInstance>> choose(Request request) {
		ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
				.getIfAvailable(NoopServiceInstanceListSupplier::new);
		return supplier.get(request).next()
				.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
	}

	private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
			List<ServiceInstance> serviceInstances) {
		Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
		if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
			((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
		}
		return serviceInstanceResponse;
	}

	private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
		if (instances.isEmpty()) {
			if (log.isWarnEnabled()) {
				log.warn("No servers available for service: " + serviceId);
			}
			return new EmptyResponse();
		}
		// TODO: enforce order?
		int pos = Math.abs(this.position.incrementAndGet());

		ServiceInstance instance = instances.get(pos % instances.size());

		return new DefaultResponse(instance);
	}

超时控制

说明:2020版本之前,超时控制由 ribbon 完成 。

spring-cloud-starter-openfeign 查看 pom 有无 ribbon来选择不同的配置方式。

旧版 ribbon 配置代码如下所示。

ribbon:
  #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ReadTimeout: 5000
  #指的是建立连接后从服务器读取到可用资源所用的时间
  ConnectTimeout: 5000

新版 FeignClientProperties

深入浅出Spring Cloud整合openfeign实现RPC_第14张图片
FeignClientConfiguration 类关键参数如下图
深入浅出Spring Cloud整合openfeign实现RPC_第15张图片
配置文件写入下面这段内容

feign:
  client:
    config:
      openfeign-8952-user:
        connectTimeout: 1000
        readTimeout: 1000

打一个断点来看是否写成功
深入浅出Spring Cloud整合openfeign实现RPC_第16张图片
调用 user5 方法,可以看到成功相应超时设置的 1s
深入浅出Spring Cloud整合openfeign实现RPC_第17张图片

更换http客户端

查看两个客户端的配置文件来比较区别

OkHttpFeignLoadBalancerConfiguration HttpClientFeignLoadBalancerConfiguration
深入浅出Spring Cloud整合openfeign实现RPC_第18张图片
区别

@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
@ConditionalOnProperty("feign.okhttp.enabled")

这两个类的区别是多了一个 matchIfMissing 那么它有什么用呢 ?

FeignLoadBalancerAutoConfiguration

@Import({ HttpClientFeignLoadBalancerConfiguration.class,
 OkHttpFeignLoadBalancerConfiguration.class,
		DefaultFeignLoadBalancerConfiguration.class })

猜想:同时满足条件只会加载 HttpClientFeignLoadBalancerConfiguration
配置文件同时开启enabled,发现先进入 HttpClientFeignLoadBalancerConfiguration,根据 @Import 机制来确定猜想。

URLConnection

java.net 包下面的http链接工具,由于有缺陷例如不支持 patch、连接池等问题最好更换,当然不换也可以用。

HttpClient

添加依赖


<dependency>
    <groupId>io.github.openfeigngroupId>
    <artifactId>feign-httpclientartifactId>
    <version>11.10version>
dependency>

进行配置

feign: 
  httpclient:
    enabled: true

enabled 点进去看到如下配置文件

    {
      "name": "feign.httpclient.enabled",
      "type": "java.lang.Boolean",
      "description": "Enables the use of the Apache HTTP Client by Feign.",
      "defaultValue": "true"
    },

HttpClientFeignLoadBalancerConfiguration 中 return new FeignBlockingLoadBalancerClient 这里打一个断点。

添加依赖后成功执行到这里说明匹配成功。

只添加依赖不开启 enable 会走到 DefaultFeignLoadBalancerConfiguration 这里面

OkHttp

添加依赖


<dependency>
    <groupId>io.github.openfeigngroupId>
    <artifactId>feign-okhttpartifactId>
    <version>11.10version>
dependency>

OkHttpFeignLoadBalancerConfiguration

深入浅出Spring Cloud整合openfeign实现RPC_第19张图片

    {
      "name": "feign.okhttp.enabled",
      "type": "java.lang.Boolean",
      "description": "Enables the use of the OK HTTP Client by Feign.",
      "defaultValue": "false"
    }

在 return new FeignBlockingLoadBalancerClient 这里打一个断点。
添加依赖后成功执行到这里说明匹配成功。
只添加依赖不开启 enable 会走到 DefaultFeignLoadBalancerConfiguration 这里面

性能比较

HTTP 连接客户端,选 HttpClient 还是 OkHttp ?

总结: OkHttp和HttpClient在性能和使用上不分伯仲,根据实际业务选择即可。

日志

1.10. Feign logging

log level 选择

public enum LogLevel {
	TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF
}
logging:
  level:
    com.example.openfeign8951.client.UserClient: debug

新增配置类

@Configuration
public class FeignLogConfiguration {

    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

  /**
   * Controls the level of logging.
   */
  public enum Level {
    /**
     * No logging.
     */
    NONE,
    /**
     * Log only the request method and URL and the response status code and execution time.
     */
    BASIC,
    /**
     * Log the basic information along with request and response headers.
     */
    HEADERS,
    /**
     * Log the headers, body, and metadata for both requests and responses.
     */
    FULL
  }

FeignClient配置

FeignAutoConfiguration中的EnableConfigurationProperties让配置文件生效

@EnableConfigurationProperties({ FeignClientProperties.class, FeignHttpClientProperties.class })

FeignClientProperties

FeignClientProperties中的FeignClientConfiguration控制

	public static class FeignClientConfiguration {
		//日志级别
		private Logger.Level loggerLevel;
		//连接超时
		private Integer connectTimeout;
		//读超时
		private Integer readTimeout;
		//重试
		private Class<Retryer> retryer;
		//错误码
		private Class<ErrorDecoder> errorDecoder;
		//过滤器
		private List<Class<RequestInterceptor>> requestInterceptors;
		//默认请求头
		private Map<String, Collection<String>> defaultRequestHeaders;
		//默认查询参数
		private Map<String, Collection<String>> defaultQueryParameters;
		//是否开启404
		private Boolean decode404;
		//decoder
		private Class<Decoder> decoder;
		//encoder
		private Class<Encoder> encoder;
		//Contract
		private Class<Contract> contract;
		//异常传播策略
		private ExceptionPropagationPolicy exceptionPropagationPolicy;
	}

decode404

在上面超时控制的时候我们配置了 FeignClientProperties 这个类里面包含一个参数decode404

深入浅出Spring Cloud整合openfeign实现RPC_第20张图片
我们来看一下true和false的区别。

feign:
  client:
    config:
      openfeign-8952-user:
        connectTimeout: 1000
        readTimeout: 1000
        decode404: false

decode404: true

{"timestamp":"2022-11-10T09:33:25.917+00:00","status":404,"error":"Not Found","message":"","path":"/aaa"}

decode404: false

feign.FeignException$NotFound: [404 ] during [GET] to [http://openfeign-8952-user/aaa] [UserClient#aaa()]: [{"timestamp":"***","status":404,"error":"Not Found","message":"","path":"/aaa"}]

不开启会得到一个错误页面,开启会得到一个相对友好的提示。

当我们学习到FeignClient这个注解的时候发现里面也有这样一个字段 boolean decode404() default false;

探究 FeignClient中decode404和FeignClientConfiguration中decode404的关联

猜想:注解上的404和配置文件404只要有一个为true则为true。

首先将注解的 decode404 和 配置文件的 decode404 都设为 false,控制台输出如下所示。

深入浅出Spring Cloud整合openfeign实现RPC_第21张图片
查找那里调用了 FeignClient
深入浅出Spring Cloud整合openfeign实现RPC_第22张图片
FeignClientsRegistrar 这个类是由 EnableFeignClients 注解 @Import(FeignClientsRegistrar.class) 导入的

深入浅出Spring Cloud整合openfeign实现RPC_第23张图片
在 getAnnotationAttributes 方法后打一个断点可以看到 Map attributes 存储了注解上定义的相关信息。
深入浅出Spring Cloud整合openfeign实现RPC_第24张图片
我们接下来看一下这个注解获取到的404对象做了什么?
深入浅出Spring Cloud整合openfeign实现RPC_第25张图片
factoryBean对象将注解中的404值拿过来了
深入浅出Spring Cloud整合openfeign实现RPC_第26张图片
接下来的调用过程如图所示
在这里插入图片描述

在这里插入图片描述
深入浅出Spring Cloud整合openfeign实现RPC_第27张图片
深入浅出Spring Cloud整合openfeign实现RPC_第28张图片
深入浅出Spring Cloud整合openfeign实现RPC_第29张图片
如果是默认的配置文件使用configure进行配置,否则使用配置文件配置。

//configureUsingConfiguration
		if (decode404) {
			builder.decode404();
		}
//configureUsingProperties
		if (config.getDecode404() != null) {
			if (config.getDecode404()) {
				builder.decode404();
			}
		}

所以我们得出结论,二者有true为true不会覆盖,相当于 或 的关系。
验证猜想
在两个 builder.decode404() 处打断点,然后分别将 注解 和 配置文件 设置为 true,看是否会跑到断点。

注解为true
深入浅出Spring Cloud整合openfeign实现RPC_第30张图片
配置文件为true
深入浅出Spring Cloud整合openfeign实现RPC_第31张图片

FeignHttpClientProperties

降级

1.6. Feign Spring Cloud CircuitBreaker Fallbacks

开启熔断配置

feign:
  circuitbreaker:
    enabled: true

fallbackFactory

fallback

在注解上指定一个降级处理类

@FeignClient(value = "openfeign-8952-user",  fallback = UserClientFallback.class)

首先我们在服务提供方写一个错误的接口,

    @GetMapping("bbb")
    public void bbb() {
        int a = 1 / 0;
    }

调用方后台结果如下所示

深入浅出Spring Cloud整合openfeign实现RPC_第32张图片
官方示例

    @Component
    static class Fallback implements TestClient {

        @Override
        public Hello getHello() {
            throw new NoFallbackAvailableException("Boom!", new RuntimeException());
        }

        @Override
        public String getException() {
            return "Fixed response";
        }

模仿给出一个实现

@Component
public class UserClientFallback implements UserClient {

    @Override
    public String getUser() {

        return "这是一个兜底方法....";
    }

    @Override
    public String aaa() {
        return "这是一个兜底方法....";
    }

    @Override
    public String bbb() {
        return "这是一个兜底方法....";
    }

    @Override
    public String getUser2(Integer id) {
        return "这是一个兜底方法....";
    }

    @Override
    public String getUser3(Integer id) {
        return "这是一个兜底方法....";
    }

    @Override
    public String getUser4(Integer id, String token) {
        return "这是一个兜底方法....";
    }

    @Override
    public String getUser5() {
        return "这是一个兜底方法....";
    }
}

访问 http://localhost:8951/bbb 发现没有效果。

在这里插入图片描述
查看官方猜想缺失部分依赖,引入依赖 resilience4j 或者 netflix-hystrix

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            <version>2.2.10.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-circuitbreaker-resilience4j -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
        </dependency>

经过测试,上面两个依赖添加任意一个都可以实现正确的降级。

在这里插入图片描述

2.2.10依赖如下图所示

深入浅出Spring Cloud整合openfeign实现RPC_第33张图片
3.0.0版本如下图所示

深入浅出Spring Cloud整合openfeign实现RPC_第34张图片
对比两个依赖项可以得出结论3.0.x版本移出了降级相关依赖。

@FeignClient 和 @EnableFeignClients

@EnableFeignClients 的作用是什么?
深入浅出Spring Cloud整合openfeign实现RPC_第35张图片
可以看到这个注解的核心就是 @Import(FeignClientsRegistrar.class) 这一行,那么他有什么作用呢?

定义一个 User 对象

public class User {

    String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public User() {
        this.name = "default";
    }

    public User(String name) {
        this.name = name;
    }
}

主启动类@Import(User.class)

深入浅出Spring Cloud整合openfeign实现RPC_第36张图片

注入对象

    @Autowired
    User user;
    
    @GetMapping("user6")
    public String getUser6() {
        return user.getName();
    }

发现输出了 default,代表走的无参构造函数。

图解+源码讲解代理对象 ReflectiveFeign 分析

这里可以看到userClient是一个代理对象。
深入浅出Spring Cloud整合openfeign实现RPC_第37张图片
我们注入的接口 UserClient 是一个代理对象,触发了动态代理的invoke方法
深入浅出Spring Cloud整合openfeign实现RPC_第38张图片

FeignAutoConfiguration

FeignClientsRegistrar

FeignClientFactoryBean

FeignClientsRegistrar中有一个registerFeignClient方法调用了 return factoryBean.getObject();

	@Override
	public Object getObject() {
		return getTarget();
	}

getTarget方法源码

	<T> T getTarget() {
	//根据beanFactory是否为null从上下文或者beanFactory中获取FeignContext
		FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
				: applicationContext.getBean(FeignContext.class);
		//配置
		Feign.Builder builder = feign(context);

		if (!StringUtils.hasText(url)) {
			if (!name.startsWith("http")) {
				url = "http://" + name;
			}
			else {
				url = name;
			}
			url += cleanPath();
			return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
		}
		if (StringUtils.hasText(url) && !url.startsWith("http")) {
			url = "http://" + url;
		}
		String url = this.url + cleanPath();
		Client client = getOptional(context, Client.class);
		if (client != null) {
			if (client instanceof FeignBlockingLoadBalancerClient) {
				// not load balancing because we have a url,
				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
				client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
			}
			builder.client(client);
		}
		//从上下文中拿Targeter 
		Targeter targeter = get(context, Targeter.class);
		return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
	}

Targeter 有两个子类

在这里插入图片描述
其中DefaultTargeter是FeignAutoConfiguration这两个地方配置的。

当我们FeignAutoConfiguration中将feign.circuitbreaker.enabled设为true的时候,会配置下面两个bean

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(CircuitBreaker.class)
	@ConditionalOnProperty("feign.circuitbreaker.enabled")
	protected static class CircuitBreakerPresentFeignTargeterConfiguration {

		@Bean
		@ConditionalOnMissingBean(CircuitBreakerFactory.class)
		public Targeter defaultFeignTargeter() {
			return new DefaultTargeter();
		}

		@Bean
		@ConditionalOnMissingBean
		@ConditionalOnBean(CircuitBreakerFactory.class)
		public Targeter circuitBreakerFeignTargeter(CircuitBreakerFactory circuitBreakerFactory) {
			return new FeignCircuitBreakerTargeter(circuitBreakerFactory);
		}
	}

CircuitBreakerFactory主要实现类如下图
在这里插入图片描述

降级失败原理

Feign类中的newInstance
在这里插入图片描述
我们知道代理类执行方法的时候会调用InvocationHandler,所以我们分别看一下两个实现。

FeignCircuitBreakerInvocationHandler

	@Override
	public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
		//省略部分代码
		String circuitName = this.feignClientName + "_" + method.getName();
		CircuitBreaker circuitBreaker = this.factory.create(circuitName);
		Supplier<Object> supplier = asSupplier(method, args);
		if (this.nullableFallbackFactory != null) {
			Function<Throwable, Object> fallbackFunction = throwable -> {
				Object fallback = this.nullableFallbackFactory.create(throwable);
				try {
					return this.fallbackMethodMap.get(method).invoke(fallback, args);
				}
				catch (Exception e) {
					throw new IllegalStateException(e);
				}
			};
			return circuitBreaker.run(supplier, fallbackFunction);
		}
		return circuitBreaker.run(supplier);
	}

FeignInvocationHandler

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	//省略部分代码
      return dispatch.get(method).invoke(args);
    }

到这里应该就明白为什么不生效了,当创建defaultFeignTargeter的时候没有对方法进行任何增强处理,自然不具备降级的能力。

Resilience4JAutoConfiguration

Resilience4JAutoConfiguration中部分代码

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = { "spring.cloud.circuitbreaker.resilience4j.enabled",
		"spring.cloud.circuitbreaker.resilience4j.blocking.enabled" }, matchIfMissing = true)
public class Resilience4JAutoConfiguration {

	@Autowired(required = false)
	private List<Customizer<Resilience4JCircuitBreakerFactory>> customizers = new ArrayList<>();

	@Bean
	@ConditionalOnMissingBean(CircuitBreakerFactory.class)
	public Resilience4JCircuitBreakerFactory resilience4jCircuitBreakerFactory() {
		Resilience4JCircuitBreakerFactory factory = new Resilience4JCircuitBreakerFactory();
		customizers.forEach(customizer -> customizer.customize(factory));
		return factory;
	}
}

参考

参考1
参考2

优化篇

参考链接

扩展

深入Feign源码吃透Spring扩展点「扩展点实战系列」- 第446篇

你可能感兴趣的:(Spring和分布式全家桶,spring,cloud,java,spring,boot)