Spring Cloud总结21.单个FeginClient禁用Hystrix

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Cloud总结21.单个FeginClient禁用Hystrix相关的知识,希望对你有一定的参考价值。


接上篇《​​20.Feign对Hystrix的支持​​》  Spring Cloud版本为Finchley.SR2版

上一篇我们简单介绍了如何开启Feign对Hystrix的支持,并演示了实现FeignClient接口以及FallbackFactory接口两种形式的降级服务。下面我们来学习一下,在存在多个FeignClient的情况下,如何禁用单个的Hystrix。
本部分官方文档:​​​https://cloud.spring.io/spring-cloud-static/Finchley.SR2/single/spring-cloud.html​

一、单个Feign Client禁用Hystrix原理

通过上一篇的学习我们可以知道,如果对Feign需要开启或者禁用Hystrix,需要在配置文件中设置feign.hystrix.enabled参数。但是要知道,feign.hystrix.enabled参数,是设置全局的Feign是否支持Hystrix,但是假如我们的应用中有多个Feign Cleint对象,想让其中一部分支持Hystrix,一部分不支持,该怎么办呢?这里就需要提到之前Feign的默认配置了。

回忆一下,Feign组件支持我们对其的核心参数进行自定义配置化,它的默认配置是FeignClientsConfiguretion,默认配置项有以下几个参数:
Decoder feignDecoder: ResponseEntityDecoder (为SpringDecoder的包装类)
Encoder feignEncoder: SpringEncoder
Logger feignLogger: Slf4jLogger
Contract feignContract: SpringMvcContract
Feign.Builder feignBuilder: HystrixFeign.Builder
Client feignClient: 如果启用的Ribbon,则为LoadBalancerFeignClient, 否则使用默认的FeignClient。
这里Decoder为请求信息的编码器,Encoder为请求信息的解码器,Logger为日志类, Contract为Feign的契约类(功能是将我们传入的接口进行解析验证,看注解的使用是否符合规范,然后返回给我们接口上各种相应的元数据),Builder为Feign的构造类(默认为HystrixFeign的Builder),然后就是Feign的Client。

我们要实现为单个FeignClient禁用Hystrix,就需要在自定义配置文件中修改其Feign.Builder参数,即修改Feign的构造类。官方文档提供的方法为:

【Spring


那为什么要修改其构造类,添加上“@Scope("prototype")”注解,就能够实现禁用单个Feign的效果呢?

默认情况下,Feign是整合了Hystrix的,我们打开Spring Cloud中Feign组件的FeignClientsConfiguretion类查看其有关Budiler的配置项(在spring-cloud-openfeign-core-2.0.2.RELEASE.jar包下):

【Spring


Budiler部分配置代码:

@Configuration
@ConditionalOnClass( HystrixCommand.class, HystrixFeign.class )
protected static class HystrixFeignConfiguration
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder()
return HystrixFeign.builder();

我们可以看到,在Feign的默认配置类中,有关于Feign构造器默认使用的就是HystrixFeign的builder,即支持Hystrix的构造器。如果我们不需要该FeignClient支持Hystrix,在自定义的配置类中,将Budiler定义为Feign自己的不支持Hystrix的构造类即可。

最后顺便说一下“@Scope("prototype")”注解,这个其实我们在之前在《Hystrix的CommandProperties配置》一章的“Hystrix线程隔离策略”小节中已经提到过,“Scope”就是Spring定义bean的生命周期的参数,常用的有singleton和prototype两种,其中:
(1)singleton:为“单一实例”的意思,一个容器中只存在一个实例,所有对该类型bean的依赖都引用这一单一实例。
(2)prototype:容器在接受到该类型的对象的请求的时候,会每次都重新生成一个新的对象给请求方。
很显然,Feign对于Builder的Bean的管理,是希望其接受到该类型对象的请求时,每次都生成新对象,

二、实例测试

理解了如何禁用翻个Feign组件的Hystrix后,我们在之前的工程上试验一下。之前Movie工程中只有一个UserFeignClient和自定义配置类TestFeignConfiguration:

【Spring


我们再添加一个UserFeignClient2以及自定义配置类TestFeignConfiguration2:

【Spring


UserFeignClient2代码:

package com.microserver.cloud;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;

import com.microserver.cloud.entity.User;
import com.microserver.config.TestFeignConfiguration2;

import feign.Param;
import feign.RequestLine;
import feign.hystrix.FallbackFactory;

/**
* 当@FeignClient有name和url还有configuration时,取值为url的地址,name只是为一个名称而已(无意义)
* 当@FeignClient只有name和configuration时,name的取值为eureka中的application项目的名称即虚拟地址
*
*/
@FeignClient(name="microserver-provider-user2",url="http://localhost:7902",configuration = TestFeignConfiguration2.class,fallbackFactory=FeignClientFallbackFactory2.class)
public interface UserFeignClient2

@RequestLine("GET /findById/id")
public User findById(@Param("id") Long id);

@RequestLine("POST /postUser")
public String postUser(@RequestBody User user);



@Component
class FeignClientFallbackFactory2 implements FallbackFactory<UserFeignClient2>

//日志对象
private static final Logger LOGGER = LoggerFactory.getLogger(FeignClientFallbackFactory2.class);

@Override
public UserFeignClient2 create(Throwable cause)
return new UserFeignClient2()

@Override
public User findById(Long id)
// 日志最好放在各个fallback方法中,而不要直接放在create方法中。
// 否则在引用启动时,就会打印该日志
FeignClientFallbackFactory2.LOGGER.info("fallback2; reason was: ", cause);

User user = new User();
user.setId(0L);
user.setName("FeignClient2 降级服务");
return user;


@Override
public String postUser(User user)
return null;

;

需要注意的是,这里的@FeignClient注解的name一定不要和之前的一样,因为如果有多个@FeignClient注解使用了相同的name属性,则注解的configuration参数会被覆盖。至于谁覆盖谁要看Spring容器初始化Bean的顺序。所以为了避免混乱,这里我们随意定义了一个name,当然这个name对应的application实例是不存在的,直接会去取url的服务地址作为请求地址。

这里我们依然为其添加了fallbackFactory参数,是为了测试引用是否有效。如果禁用有效,就不会进入降级方法。配置类TestFeignConfiguration2代码:

package com.microserver.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import feign.Feign;

@Configuration
public class TestFeignConfiguration2
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder()
return Feign.builder();


@Bean
public Contract feignContract()
return new feign.Contract.Default();


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

这里设置Builder为Feign的原始Builder。然后在Controller中添加一个新的请求响应方法,来试验UserFeignClient2:

@Autowired
private UserFeignClient2 userFeignClient2;

@GetMapping("/movie2/id")
public User findUserById2(@PathVariable Long id)
return userFeignClient2.findById(id);

然后我们启动之前的服务提供者user工程、服务消费者movie工程以及eureka Server注册中心,访问“movie/1”服务以及“movie2/1”是好的:

【Spring


【Spring


然后关掉服务提供者user工程,再次访问“movie/1”服务,可以看到此时进入了降级服务中:

【Spring


然后我们再访问“movie2/1”服务,发现没有进入降级服务,而是进行了报错:

【Spring


【Spring


说明我们为单个FeignClient禁用Hystrix是成功的。

三、为单个FeignClient禁用Hystrix的意义

虽然我们上面实现了为单个FeignClient禁用Hystrix的效果,但是看起来好像没有什么实现的意义,貌似看起来有Hystrix的支持更加稳妥一些。那么究竟什么样的场景下,需要对单个FeignClient的Hystrix进行禁用呢?

spring cloud通过feign client进行服务之间调用的时候,默认不会进行重试,这样会有一个问题,比如你的服务在滚动升级重启的时候,feign的调用将直接失败,但其实滚动重启情况下,重启了一个服务实例,此时还有另外一个服务实例是可用的,应该允许自动均衡策略重试请求发送到另外一个可用的服务实例上去。

上面就是feign的重复请求策略,hystrix的熔断机制可能会导致fegin的重复请求策略无效(重复请求的超时时间必须小于hystrix的熔断时间)。所以在某些情况下就需要选择关闭fegin的hystrix。

参考:《51CTO学院Spring Cloud高级视频》

单个项目中的多个 Spring Cloud GCP 库导致 NoClassDefFoundError

【中文标题】单个项目中的多个 Spring Cloud GCP 库导致 NoClassDefFoundError【英文标题】:Multiple Spring Cloud GCP libraries in single project causes NoClassDefFoundError 【发布时间】:2020-01-30 00:09:04 【问题描述】:

如果我使用单个 Spring Cloud GCP 库,例如implementation("org.springframework.cloud:spring-cloud-gcp-starter-sql-postgresql:1.1.1.RELEASE") 和属性:spring.cloud.gcp.credentials.encoded-key= 用于凭据...一切正常。

但如果我还想说通过 implementation("org.springframework.cloud:spring-cloud-gcp-starter-pubsub:1.1.3.RELEASE") 将 pub/sub 添加到我的项目中,那么我会得到以下异常。

我试过了:

    使用不同的 Java 版本 11 和 12 不同的Spring Cloud GCP库作为“第二”库implementation("org.springframework.cloud:spring-cloud-gcp-starter:1.1.3.RELEASE")implementation("org.springframework.cloud:spring-cloud-gcp-starter-logging:1.1.3.RELEASE") 使用spring.cloud.gcp.credentials.location 代替spring.cloud.gcp.credentials.encoded-key 使用管理员服务帐户确保不是 IAM 角色问题。

implementation("org.springframework.cloud:spring-cloud-gcp-starter:1.1.3.RELEASE")implementation("org.springframework.cloud:spring-cloud-gcp-starter-logging:1.1.3.RELEASE")

只要我包含多个这些 Spring Cloud GCP 库,同样的 SocketFactory 错误就会出现。从文档中,它应该可以正常工作。第二个库应该只使用相同的凭据。

如果我恢复到单个库,那么它工作正常。

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.autoconfigure.jdbc.DataSourceProperties]: Factory method 'cloudSqlDataSourceProperties' threw exception; nested exception is java.lang.NoClassDefFoundError: com/google/cloud/sql/core/CoreSocketFactory
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:622) ~[spring-beans-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    ... 171 common frames omitted
Caused by: java.lang.NoClassDefFoundError: com/google/cloud/sql/core/CoreSocketFactory
    at org.springframework.cloud.gcp.autoconfigure.sql.GcpCloudSqlAutoConfiguration$CloudSqlDataSourcePropertiesConfiguration.cloudSqlDataSourceProperties(GcpCloudSqlAutoConfiguration.java:209) ~[spring-cloud-gcp-autoconfigure-1.1.3.RELEASE.jar:1.1.3.RELEASE]
    at org.springframework.cloud.gcp.autoconfigure.sql.GcpCloudSqlAutoConfiguration$CloudSqlDataSourcePropertiesConfiguration$$EnhancerBySpringCGLIB$$4f5495da.CGLIB$cloudSqlDataSourceProperties$0(<generated>) ~[spring-cloud-gcp-autoconfigure-1.1.3.RELEASE.jar:1.1.3.RELEASE]
    at org.springframework.cloud.gcp.autoconfigure.sql.GcpCloudSqlAutoConfiguration$CloudSqlDataSourcePropertiesConfiguration$$EnhancerBySpringCGLIB$$4f5495da$$FastClassBySpringCGLIB$$58c2377.invoke(<generated>) ~[spring-cloud-gcp-autoconfigure-1.1.3.RELEASE.jar:1.1.3.RELEASE]
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) ~[spring-core-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363) ~[spring-context-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    at org.springframework.cloud.gcp.autoconfigure.sql.GcpCloudSqlAutoConfiguration$CloudSqlDataSourcePropertiesConfiguration$$EnhancerBySpringCGLIB$$4f5495da.cloudSqlDataSourceProperties(<generated>) ~[spring-cloud-gcp-autoconfigure-1.1.3.RELEASE.jar:1.1.3.RELEASE]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:567) ~[na:na]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.1.4.RELEASE.jar:5.1.4.RELEASE]
    ... 172 common frames omitted
Caused by: java.lang.ClassNotFoundException: com.google.cloud.sql.core.CoreSocketFactory
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583) ~[na:na]
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178) ~[na:na]
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521) ~[na:na]
    ... 183 common frames omitted

我还在这里创建了一个 Github 问题:https://github.com/spring-cloud/spring-cloud-gcp/issues/1948

【问题讨论】:

【参考方案1】:

由于从 Maven Central 复制粘贴导入而未检查版本,因此使用了不同版本的库。

使用的版本包括1.1.1.RELEASE1.1.3.RELEASE。一次只能使用其中一个版本号。

重构代码以消除此错误的可能性:

implementation("org.springframework.cloud:spring-cloud-gcp-starter-sql-postgresql:$springCloudGCPVersion")
implementation("org.springframework.cloud:spring-cloud-gcp-starter-pubsub:$springCloudGCPVersion")
implementation("org.springframework.cloud:spring-cloud-gcp-dependencies:$springCloudGCPVersion")
implementation("org.springframework.cloud:spring-cloud-gcp-starter:$springCloudGCPVersion")

【讨论】:

使用BOM也比较好。事实上,当使用 Spring Boot 时,它应该只导入 Spring Cloud BOM。我建议从 start.spring.io 生成初始项目。这会产生这样的 gradle 构建:gist.github.com/saturnism/7114a473866aa46fdec8405589d579de

以上是关于Spring Cloud总结21.单个FeginClient禁用Hystrix的主要内容,如果未能解决你的问题,请参考以下文章

10.Spring-Cloud-Hystrix之熔断监控Hystrix Dashboard单个应用

基于Spring Cloud Alibaba Nacos进行单个DataID的配置读取

基于Spring Cloud Alibaba Nacos进行单个DataID的配置读取

最新版Spring Cloud Alibaba微服务架构-Openfeign服务调用篇

最新版Spring Cloud Alibaba微服务架构-Openfeign服务调用篇

SpringCloud升级之路2020.0.x版-21.Spring Cloud LoadBalancer简介