自学内容网 自学内容网

SpringCloudOpenFeign的详解

1. SpringCloud OpenFeign的特性

1. 概念

  1. Feign是一个声明式web Rest服务客户端。它使编写web服务客户端更容易
  2. 要使用Feign,请创建一个接口并对其使用注解进行标注
  3. 它具有可插入注释支持,包括Feign注释和JAX-RS注释
  4. Feign还支持可插拔编码器和解码器
  5. Spring Cloud增加了对Spring MVC注释的支持,并支持使用与Spring Web中默认使用的HttpMessageConverters相同的HttpMessageConverters
  6. Spring Cloud集成了Eureka、Spring Cloud CircuitBreaker以及Spring Cloud LoadBalancer,在使用Feign时提供负载均衡的http客户端

2. 简单使用

  1. 引入依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
  2. 启动类

    @SpringBootApplication
    @EnableFeignClients
    public class Application {
    public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
    }
    }
    
    
  3. 标注Feign客户端

    @FeignClient("stores")
    public interface StoreClient {
    @RequestMapping(method = RequestMethod.GET, value = "/stores")
    List<Store> getStores();
    
    @GetMapping("/stores")
    Page<Store> getStores(Pageable pageable);
    
    @PostMapping(value = "/stores/{storeId}", consumes = "application/json")
    Store update(@PathVariable("storeId") Long storeId, Store store);
    
    @DeleteMapping("/stores/{storeId:\\d+}")
    void delete(@PathVariable Long storeId);
    }
    
  4. 案例总结

    1. 在@FeignClient注释中,字符串值(上面的“stores”)是一个任意的客户端名称,用于创建Spring Cloud LoadBalancer客户端

    2. 还可以使用URL属性(URL的绝对值或直接写主机名)指定请求的url路径

      1. 之前的版本,FeignClient的url属性可以单独使用,不需要再写name属性

      2. 现在,就算使用url属性,仍然需要name属性]

        @FeignClient(name = "${feign.name}", url = "${feign.url}")
        public interface StoreClient {
        }
        
    3. Spring容器中bean的名称是接口的完全限定名称。要指定自己的别名值,可以使用@FeignClient注释的qualifiers值

    4. 上面的负载均衡客户端将需要发现“stores”服务的物理地址

      1. 如果您的应用程序是Eureka客户端,那么它将在Eureka服务注册表中解析服务
      2. 如果不想使用Eureka,可以使用SimpleDiscoveryClient在外部配置中配置服务器列表
      3. 或者使用其他的客户端(例如Nacos,Consul),提供对应服务的DiscoveryClient实现该实现提供指定服务的实例
    5. Spring Cloud OpenFeign支持Spring Cloud LoadBalancer阻塞模式的所有可用特性

    6. @EnableFeignClients默认扫描标注类的包下的FeignCLient接口,如果想指定包路径或者指定FeignClient接口,可以使用

      1. @EnableFeignClients(basePackages = "com.example.clients")
      2. @EnableFeignClients(clients = InventoryServiceFeignClient.class)
    7. 默认情况下,Feign客户端是立即加载的,它还可以支持AOT,如果不想他们立即加载,可以设置为懒加载

      1. spring.cloud.openfeign.lazy-attributes-resolution=true

3. 覆盖Feign的默认配置

  1. Spring Cloud的Feign支持的一个中心概念: 命名(具名)客户端
  2. 每个Feign客户端都是组件集成的一部分,这些组件协同工作来按需请求远程服务器,我们可以使用@FeignClient注解为客户端提供一个名称
  3. Spring Cloud根据需要使用FeignClientsConfiguration配置类为每个命名的客户端创建一个独立的新的ApplicationContext。包含feign.Decoder,feign.Encoder,feign.Contract核心组件,这些都是在FeignClientsConfiguration配置类中提供的默认组件
    1. 我们可以通过使用@FeignClient注解的contextId属性来覆盖该Feign客户端的名称(name属性)
  4. Spring Cloud允许我们通过使用@FeignClient声明额外的配置来完全控制FeignClient,覆盖FeignClientsConfiguration配置的组件实例,使用@FeignClient的configuration属性
    1. 注意: 在这种使用configuration属性情况下FooConfiguration不需要加@Configuration注解
    2. 因为如果它是一个Bean,那么所有的FeignClient默认配置类FeignClientsConfiguration的对应组件配置就都不会生效,因为它默认配置类都标注了@ConditionalOnMissingBean,因为该Bean存在所有FeignClient独立的ApplicationContext的父容器中,从而使用这个配置类中配置的组件,这样它就覆盖了默认的配置
    3. 无论如何,它最终都会通过AnnotationConfigRegistry#register(applicatioContext)进行注册,最终扫描该配置类,类似与启动类的配置类效果,最终在调用FeignClient独立的ApplicationContext的refresh方法时进行解析该配置
    4. 因此,需要注意,该配置类最好不加@Configuration,这样的话,该配置仅为了指定的FeignClient生效,否则将覆盖所有FeignClient的默认配置,除非自己确实需要这样去做
@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {
}
public class FooConfiguration{
// 等等其他组件覆盖FeignClientsConfiguration的默认配置
    @Bean
public Encoder feignEncoder(ObjectProvider<AbstractFormWriter> formWriterProvider,ObjectProvider<HttpMessageConverterCustomizer> customizers) {
return springEncoder(formWriterProvider, encoderProperties, customizers);
}
}

4. Feign提供和支持的Bean组件

1. SpringCloud默认提供下面这些Bean

  1. Decoder
    1. beanName: feignDecoder
    2. beanType: ResponseEntityDecoder
  2. Encoder
    1. beanName: feignEncoder
    2. beanType: SpringEncoder
  3. Logger
    1. beanName: feignLogger
    2. beanType: Slf4jLogger
  4. 链路追踪相关的增强器
    1. MicrometerObservationCapability
    2. MicrometerCapability
  5. 缓存的增强器
    1. CachingCapability
  6. Contract
    1. beanName: feignContract
    2. beanType: SpringMvcContract
  7. Feign.Builder
    1. beanName: feignBuilder
    2. beanType: FeignCircuitBreaker.Builder
  8. Client (重要)
    1. beanName: feignClient
    2. beanType: FeignBlockingLoadBalancerClient(如果存在Spring Cloud LoadBalancer依赖),否则使用默认的 Client.Default,使用HttpURLConnection处理
    3. 此外,可以修改默认的Client,因为默认的处理请求的效率很低
      1. 开启okhttp:
        1. spring.cloud.openfeign.okhttp.enabled: true
      2. 开启http2client
        1. spring.cloud.openfeign.http2client.enabled: true
      3. 开启httpclient5
        1. spring.cloud.openfeign.httpclient.hc5.enabled: true
      4. 注意: 都需要导入对应的依赖才行

2. Feign没有提供默认的Bean,但是会从容器中加载的Bean

  1. Logger.Level 日志级别
  2. Retryer 重试机制,默认禁止重试
  3. ErrorDecoder 异常解码器,解析之后可以得到一个异常信息
  4. Request.Options 请求参数配置信息
  5. Collection<RequestInterceptor> 请求拦截器
  6. SetterFactory
  7. QueryMapEncoder 将参数转换为map的编码器
  8. Capability (MicrometerObservationCapability and CachingCapability)增强器

3. 覆盖Feign默认的Bean组件

  1. 方式一

    1. 全局覆盖
    @Configuration
    public class FooConfiguration {
    @Bean
    public Contract feignContract() {
    return new feign.Contract.Default();
    }
    
    @Bean
    public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
    return new BasicAuthRequestInterceptor("user", "password");
    }
    }
    
  2. 方式二

    1. 单独覆盖FeignClient
    @FeignClient(name = "stores", configuration = FooConfiguration.class)
    public interface StoreClient {
    }
    public class FooConfiguration{
    @Bean
    public Contract feignContract() {
    return new feign.Contract.Default();
    }
    }
    

5. Feign的yaml/properties配置(等价于@Bean配置)

1. 默认情况,配置类和yaml配置效果一样,都是给核心Feign进行配置,配置文件优先于@Bean配置,如果同时存在,配置文件中配置的覆盖@Bean配置的
1. 如果需要让@Bean的优先,则使用`spring.cloud.openfeign.client.default-to-properties=false`来禁用默认的配置优先
spring:
cloud:
openfeign:
client:
config:
    # feignClient名称
feignName:
    // @FeignClient的url,优先取用@FeignClient的URL,再取用配置属性文件中设置的url,最后使用默认的url,http://服务名
                        url: http://remote-service.com
                        // 设置连接超时时间
connectTimeout: 5000
// 设置请求超时时间
readTimeout: 5000
// 设置日志级别
loggerLevel: full
// 设置异常解码器
errorDecoder: com.example.SimpleErrorDecoder
// 设置重试机制
retryer: com.example.SimpleRetryer
// 添加默认的请求参数
defaultQueryParameters:
query: queryValue
// 添加默认的请求头
defaultRequestHeaders:
header: headerValue
// 设置请求拦截器
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
// 设置响应拦截器
responseInterceptor: com.example.BazResponseInterceptor
// 设置是否处理404
dismiss404: false
// 设置编码器
encoder: com.example.SimpleEncoder
// 设置解码器
decoder: com.example.SimpleDecoder
 // Contract 接口定义了如何根据 Java 接口的注解创建请求和解析响应
                        // Contract 接口是 Feign 的一个核心接口,它可以自定义 Feign 客户端与服务端之间的契约规则,例如支持不同的注解风格、参数传递方式等
                        // 通过实现自定义的 Contract 接口,可以更灵活地定制 Feign 客户端与服务端之间的通信行为
                        // 该类负责解析一些注解SpringMVC的注解信息,校验哪些注解是否标注错误,解析方法上的RequestMapping注解,参数上的Param注解等
                        // 解析到的注解信息,然后交给RequestTemplate
                        // 例如:
                        //  @CollectionFormat: 集合类型的数据如何传递,CSV,\t,|分割等等
                        //  @RequestMapping解析,@Param,@SpringQueryMap,@QueryMap,@RequestParam处理
                        // 该类还包含对应注解的参数解析器
contract: com.example.SimpleContract
// 设置将以上描述的组件进行增强的接口
        // 该接口的定义: A enrich (A a){return a;},给你指定参数,让你增强,返回增强后的组件
capabilities:
- com.example.FooCapability
- com.example.BarCapability
                        // 设置将请求参数对象转换为map的编码器
queryMapEncoder: com.example.SimpleQueryMapEncoder
# 开启链路追踪
micrometer.enabled: false

6. Feign对CircuitBreaker熔断的支持

  1. 如果存在CircuitBreaker实现,可以使用配置spring.cloud.openfeign.circuitbreaker.enabled=true启用熔断

  2. 如果想禁用FeignClient的熔断支持,可以配置一个Feign.Builder的Bean,该Builder创建的代理对象不会处理熔断

    @Configuration
    public class FooConfiguration {
    @Bean
    // 注意: 该Bean要注册为原型的,对于每一个FeignClient都互不影响
    @Scope("prototype")
    public Feign.Builder feignBuilder() {
    return Feign.builder();
    }
    }
    
    // 如果开启了熔断,那么默认注册的Builder对象为FeignCircuitBreaker.builder,该对象创建的代理对象支持熔断机制,如果需要禁用,我们需要配置Feign.builder来禁用该Builder的注册
    @Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    @ConditionalOnBean(CircuitBreakerFactory.class)
    public Feign.Builder circuitBreakerFeignBuilder() {
        return FeignCircuitBreaker.builder();
    }
    
  3. 对于断路器的名称格式为: <feignClientClassName>#<calledMethod>(<parameterTypes>),例如``FooFeignClient#bar(),如果要修改默认的名称格式,提供一个CircuitBreakerNameResolver `Bean

    @Configuration
    public class FooConfiguration {
    @Bean
    public CircuitBreakerNameResolver circuitBreakerNameResolver() {
    return (String feignClientName, Target<?> target, Method method) -> feignClientName + "_" + method.getName();
    }
    }
    
  4. 配置属性文件配置熔断

    spring:
      cloud:
        openfeign:
          circuitbreaker:
            enabled: true
            alphanumeric-ids:
              enabled: true
    resilience4j:
     # 熔断配置
      circuitbreaker:
        instances:
          spring-cloud-order:
            minimumNumberOfCalls: 69
      # 时间超时限制配置
      timelimiter:
        instances:
          spring-cloud-order:
            timeoutDuration: 10s
    
    1. 如果想切换为以前2022.0.0版本的熔断名称,可以使用spring.cloud.openfeign.circuitbreaker.alphanumeric-ids.enabled: false配置
  5. Feign的熔断机制-服务降级

    @FeignClient(name = "test", url = "http://localhost:${server.port}/", fallback = Fallback.class)
    protected interface TestClient {
    
    @GetMapping("/hello")
    Hello getHello();
    
    @GetMapping("/hellonotfound")
    String getException();
    
    }
    // 服务降级处理类
    @Component
    static class Fallback implements TestClient {
    
    @Override
    public Hello getHello() {
    throw new NoFallbackAvailableException("Boom!", new RuntimeException());
    }
    
    @Override
    public String getException() {
    return "Fixed response";
    }
    
    }
    
    @FeignClient(name = "testClientWithFactory", url = "http://localhost:${server.port}/",
    fallbackFactory = TestFallbackFactory.class)
    protected interface TestClientWithFactory {
    
    @GetMapping("/hello")
    Hello getHello();
    
    @GetMapping("/hellonotfound")
    String getException();
    
    }
    
    @Component
    static class TestFallbackFactory implements FallbackFactory<FallbackWithFactory> {
    
    @Override
    public FallbackWithFactory create(Throwable cause) {
    return new FallbackWithFactory();
    }
    
    }
    
    static class FallbackWithFactory implements TestClientWithFactory {
    
    @Override
    public Hello getHello() {
    throw new NoFallbackAvailableException("Boom!", new RuntimeException());
    }
    
    @Override
    public String getException() {
    return "Fixed response";
    }
    
    }
    
    1. 注意: 在当使用Feign和Spring Cloud CircuitBreaker进行服务降级时,ApplicationContext中有多个相同类型的bean,一个是FeignClient,一个为兜底的处理类。这将导致@Autowired无法工作,因为存在多个Bean。为了解决这个问题,Spring Cloud OpenFeign将所有Feign实例(FeignClient)标记为@Primary,这样Spring框架就知道要注入哪个bean

      1. 但是在某些情况下,这可能是不可取的,因为我们有自己所指定的Bean,因此要关闭此行为,请将@FeignClient的主要属性设置为false

        @FeignClient(name = "hello", primary = false)
        public interface HelloClient {
        }    
        

7. Feign对链路追踪Micrometer 的支持

  1. Feign提供了一个增强机制Capability,它可以对Fegin的核心组件进行加工,接收核心组件参数,返回类型一样的组件

  2. Micrometer链路追踪就是通过这个实现

    # 所有的FeignClient生效
    spring.cloud.openfeign.micrometer.enabled=true
    # 单个FeignClient生效
    spring.cloud.openfeign.client.config.feignName(Feign客户端名称).micrometer.enabled=true
    
    1. 通过以上配置,会自动导入两个Bean
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnProperty(name = "spring.cloud.openfeign.micrometer.enabled", matchIfMissing = true)
    @ConditionalOnClass({ MicrometerObservationCapability.class, MicrometerCapability.class, MeterRegistry.class })
    @Conditional(FeignClientMicrometerEnabledCondition.class)
    protected static class MicrometerConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnBean(type = "io.micrometer.observation.ObservationRegistry")
    public MicrometerObservationCapability micrometerObservationCapability(ObservationRegistry registry) {
    return new MicrometerObservationCapability(registry);
    }
    
    @Bean
    @ConditionalOnBean(type = "io.micrometer.core.instrument.MeterRegistry")
    @ConditionalOnMissingBean({ MicrometerCapability.class, MicrometerObservationCapability.class })
    public MicrometerCapability micrometerCapability(MeterRegistry registry) {
    return new MicrometerCapability(registry);
    }
    
    }
    
    1. 我们可以根据需要自定设置配置属性启用或者自己手动配置下面两个Bean

8. Feign的细节配置

  1. 如果存在多个相同服务名称的FeignClient,我们需要使用contextId属性进行表示,防止Bean名称冲突

    @FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class)
    public interface FooClient {
    }
    
    @FeignClient(contextId = "barClient", name = "stores", configuration = BarConfiguration.class)
    public interface BarClient {
    }
    
  2. 如果想让某一个FeignClient的独立ApplicationContext不继承父容器的Bean信息,这样也可以达到配置隔离,但是可能会有,所有的Bean都有FeignClient自身的ApplicationContext产生,我们可以使用FeignClientConfigurer配置

    @Configuration
    public class CustomConfiguration {
    @Bean
    public FeignClientConfigurer feignClientConfigurer() {
    return new FeignClientConfigurer() {
    @Override
    public boolean inheritParentConfiguration() {
     return false;
    }
    };
    }
    }
    
  3. 默认情况下,Feign不会解码"/“,例如”/“编码为”%2F",“%2F"解码为”/“,可以通过spring.cloud.openfeign.client.decodeSlash: false则会进行解码,将”%2F"解码为"/"

  4. SpringEncoder编码器,将请求参数编码为请求体对象,编码为请求体默认字符集固定为UTF-8,如果需要修改这种行为,则spring.cloud.openfeign.encoder.charset-from-content-type: true

  5. 如果想使用自定义的FeignClient客户端,不使用Feign自动创建的代理对象,我们可以仿照源码操作

    @Import(FeignClientsConfiguration.class)
    class FooController {
    
    private FooFeignClient fooClient;
    
    private FooFeignClient adminClient;
    
    @Autowired
    public FooController(Client client, Encoder encoder, Decoder decoder, Contract contract, MicrometerObservationCapability micrometerObservationCapability) {
    this.fooClient = Feign.builder().client(client)
    .encoder(encoder)
    .decoder(decoder)
    .contract(contract)
    .addCapability(micrometerObservationCapability)
    .requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
    .target(FooClient.class, "https://PROD-SVC");
    
    this.adminClient = Feign.builder().client(client)
    .encoder(encoder)
    .decoder(decoder)
    .contract(contract)
    .addCapability(micrometerObservationCapability)
    .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
    .target(FooClient.class, "https://PROD-SVC");
    }
    }
    
  6. 在Feign中,FeignClient接口是可以作为模板进行继承和实现的,例如

    1. FeignClient接口不应该在服务器(接收请求端)和客户端(发送请求端)之间共享,并且不在支持类级别上的@RequestMapping标注
    public interface UserService {
    @GetMapping("/users/{id}")
    User getUser(@PathVariable("id") long id);
    }
    @RestController
    public class UserResource implements UserService {
    
    }
    @FeignClient("users")
    public interface UserClient extends UserService {
    
    }
    
  7. 开启请求响应GZip压缩(OkHttp默认支持GZip压缩,如果启用了OkHttp,则默认开启)

    # 开启响应的GZIP压缩功能
    spring.cloud.openfeign.compression.response.enabled=true
    # 开启请求的GZIP压缩功能
    spring.cloud.openfeign.compression.request.enabled=true
    # 触发压缩的请求数据类型
    spring.cloud.openfeign.compression.request.mime-types=text/xml,application/xml,application/json
    # 最小触发的压缩大小KB
    spring.cloud.openfeign.compression.request.min-request-size=2048
    
  8. Feign的日志配置

    1. yml配置

      1. 配置指定FeignClient的日志级别

        logging.level.project.user.UserFeignClient: DEBUG

    2. 使用@Bean配置

      1. 只需要提供Logger.Level这个Bean,并给定日志级别
        1. 全局的和FeignClient独立的参考覆盖默认配置
      @Configuration
      public class FooConfiguration {
      @Bean
      Logger.Level feignLoggerLevel() {
      return Logger.Level.FULL;
      }
      }
      
  9. Feign的缓存配置

  10. 如何开启了缓存 @EnableCaching,会自动增强InvocationHandlerFactory,该类是增强创建代理对象执行器InvocationHandler的厂,因此我们创建代理对象的InvocationHandler也具有了缓存的功能

    public class CachingCapability implements Capability {
    
        // Spring对缓存创建代理对象的拦截器,只需要执行该拦截器的invoke方法就会触发缓存操作
    private final CacheInterceptor cacheInterceptor;
    
    public CachingCapability(CacheInterceptor cacheInterceptor) {
    this.cacheInterceptor = cacheInterceptor;
    }
    
    @Override
    public InvocationHandlerFactory enrich(InvocationHandlerFactory invocationHandlerFactory) {
    return new FeignCachingInvocationHandlerFactory(invocationHandlerFactory, cacheInterceptor);
    }
    
    }
    
    public class CachingCapability implements Capability {
    
    private final CacheInterceptor cacheInterceptor;
    
    public CachingCapability(CacheInterceptor cacheInterceptor) {
    this.cacheInterceptor = cacheInterceptor;
    }
    
    @Override
    public InvocationHandlerFactory enrich(InvocationHandlerFactory invocationHandlerFactory) {
    return new FeignCachingInvocationHandlerFactory(invocationHandlerFactory, cacheInterceptor);
    }
    
    }
    
  11. 使用缓存

    public interface DemoClient {
    @GetMapping("/demo/{filterParam}")
    // 缓存接口的结果
        @Cacheable(cacheNames = "demo-cache", key = "#keyParam")
    String demoEndpoint(String keyParam, @PathVariable String filterParam);
    }
    
  12. 转发报文头的支持

    spring.cloud.loadbalancer.x-forwarded.enabled=true
    
  13. OAuth2的支持

    spring.cloud.openfeign.oauth2.enabled=true
    
  14. 注解支持

    1. @QueryMap

      1. 将请求参数自动转换为Map,使用Map接收参数
       @RequestLine("GET /api/data")
      void getData(@QueryMap Map<String, Object> queryParams);
      
    2. @SpringQueryMap

      1. 将请求参数自动转换为JavaBean对象
      @GetMapping(path = "/demo")
      String demoEndpoint(@SpringQueryMap Params params);
      
    3. @MatrixVariable矩阵变量

      @GetMapping("/objects/links/{matrixVars}")
      Map<String, List<String>> getObjects(@MatrixVariable Map<String, List<String>> matrixVars);
      
    4. @CollectionFormat

      1. 将集合类型的参数以指定的格式进行编码
      @FeignClient(name = "demo")
      protected interface DemoFeignClient {
      
          @CollectionFormat(feign.CollectionFormat.CSV)
          @GetMapping(path = "/test")
          ResponseEntity performRequest(List<String> test);
      
          @RequestLine("GET /api/resource?ids={ids}")
          void getResource(@Param(value = "ids", collectionFormat = CollectionFormat.CSV) List<String> ids);
      
      }
      
  15. FeignClinet中URL的配置

```
// 指定url发送请求,不支持负载均衡,并且url不会再刷新(即使配置属性发生改变)
@FeignClient(name="testClient", url="http://localhost:8081")
# 等价于下面,如果同时存在,则@FeignClient生效
spring.cloud.openfeign.client.config.testClient.url=http://localhost:8081

// 组合配置,请求到指定的url,该url可以被刷新,当配置属性发生改变的时候,该FeignClient请求的url会进行刷新
@FeignClient(name="testClient")
spring.cloud.openfeign.client.config.testClient.url=http://localhost:8081
# 开启URL刷新机制,中将该URL当成事一个Bean,当发送请求的时候,从这个Bean中获取url
spring.cloud.openfeign.client.refresh-enabled=true

// 最简配置,按照Feign客户端名称解析,所有的系统自动处理,支持负载均衡
@FeignClient(name="testClient")
```
  1. 对Feign构造好的Request对象进行加工转换的扩展类LoadBalancerFeignRequestTransformer

    1. 该对象返回的Request对象才是最终发送请求的Reqeust对象
    @Bean
    public LoadBalancerFeignRequestTransformer transformer() {
    return new LoadBalancerFeignRequestTransformer() {
    
    @Override
    public Request transformRequest(Request request, ServiceInstance instance) {
    Map<String, Collection<String>> headers = new HashMap<>(request.headers());
    headers.put("X-ServiceId", Collections.singletonList(instance.getServiceId()));
    headers.put("X-InstanceId", Collections.singletonList(instance.getInstanceId()));
    return Request.create(request.httpMethod(), request.url(), headers, request.body(), request.charset(),
    request.requestTemplate());
    }
    };
    }
    

2. SpringCloud LoadBalance负载均衡

1. 概念

  1. Spring Cloud提供了自己的客户端负载均衡器的抽象和实现
  2. 对于负载均衡机制,增加了ReactiveLoadBalancer接口,并为其提供了基于轮询和随机的实现。
  3. 为了从响应式中选择服务实例,使用了ServiceInstanceListSupplier,支持ServiceInstanceListSupplier基于服务发现的实现,它使用类路径中可用的DiscoveryClient(注册中心)中检索可用实例
  4. 负载均衡官网

2. 功能

  1. 依赖

    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
    
  2. 开启与禁用负载均衡(默认启用)

    spring.cloud.loadbalancer.enabled=false
    
  3. 默认情况下LoadBalancerClient是懒加载的,只有在第一次负载均衡的时候才会加载该实例

    public class BlockingLoadBalancerClient implements LoadBalancerClient {
    @Override
    public <T> ServiceInstance choose(String serviceId, Request<T> request) {
    // 这里才会加载loadBalancerClient
    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();
    }
    }
    
    1. 如果想要LoadBalancerClient立即加载,则可以使用spring.cloud.loadbalancer.eager-load.clients配置

      spring.cloud-loadbalancer.eager-load.clients[0]=my-first-client
      spring.cloud-loadbalancer.eager-load.clients[1]=my-second-client
      
      // 实现原理
      @Bean
      public LoadBalancerEagerContextInitializer loadBalancerEagerContextInitializer(
      LoadBalancerClientFactory clientFactory, 
      // 该配置就是spring.cloud-loadbalancer.eager-load,而下面就是获取该配置的客户端进行加载
      LoadBalancerEagerLoadProperties properties) {
      return new LoadBalancerEagerContextInitializer(clientFactory, properties.getClients());
      }
      // 监听容器准备完成事件
      public LoadBalancerEagerContextInitializer implements ApplicationListener<ApplicationReadyEvent> {
      @Override
      public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
      serviceNames.forEach(factory::getInstance);
      }
      }
      
  4. 切换默认的负载均衡算法

    1. 默认的负载均衡算法为轮训

      @Configuration(proxyBeanMethods = false)
      @ConditionalOnDiscoveryEnabled
      public class LoadBalancerClientConfiguration {
      @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);
      }
      }
      
    2. 如果需要切换负载均衡算法,则可以提供该Bean

      1. 设置为随机算法

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

原文地址:https://blog.csdn.net/JavaMyDream/article/details/139232165

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!