Spring Cloud LoadBalancer

ReactiveLoadBalancer与ServiceInstanceListSupplier

Spring Cloud提供了client的load-balance抽象和实现。在load-balance机制中添加了ReactiveLoadBalancer接口,并且为其提供了Round-Robin-basedRandom实现。

为了从反应式服务中选择服务实例,使用了ServiceInstanceListSupplier接口,Spring Cloud使用ServiceInstanceListSupplierservice-discovery-based的实现来从使用类路径中可用的DiscoveryClientServiceDiscovery中检索可用服务实例。

RoundRobinLoadBalancer

RoundRobinLoadBalancer是默认情况下使用的ReactiveLoadBalancer的实现类。可以自定义LoadBalancer的配置来切换不同的ReactiveLoadBalancer实现。

切换自己的Spring Cloud LoadBalancer配置

可以使用@LoadBalancerClient来切换自己的load-balancer client配置,如下:

@Configuration
//value的值指定了在给定的load-balancer client配置下,要发送请求到哪个服务(服务id)
@LoadBalancerClient(value = "stores", configuration = CustomLoadBalancerConfiguration.class)
public class MyConfiguration {
​
    @Bean
    @LoadBalanced
    public WebClient.Builder loadBalancedWebClientBuilder() {
        return WebClient.builder();
    }
}

或者通过@LoadBalancerClients来定义多个配置:

@LoadBalancerClients({
    @LoadBalancerClient(value = "stores", configuration = StoresLoadBalancerClientConfiguration.class),
    @LoadBalancerClient(value = "customers", configuration = CustomersLoadBalancerClientConfiguration.class)
})

作为@LoadBalancerClient@LoadBalancerClients注释的参数的类不应该使用@Configuration进行注释,也不应在组件扫描之外。

为了更容易地使用自己的LoadBalancer配置,可以使用 ServiceInstanceListSupplier类的 builder()方法
也可以使用Spring Cloud预定义的其他配置方案,来替换默认配置,例如设置 spring.cloud.loadbalancer.configurations属性为 zone-preference来使用带有缓存的 ZonePreferenceServiceInstanceListSupplier,或设置为 health-check来使用带有缓存的 HealthCheckServiceInstanceListSupplier

可以利用这个特性来实例化不同的ServiceInstanceListSupplierReactorLoadBalancer的实现来覆盖默认的配置。

Zone-Based Load-Balancing

为了实现基于区域的load-balancing,Spring Cloud提供了ZonePreferenceServiceInstanceListSupplier,使用特定DiscoveryClient的区域配置(例如,eureka.instance.metadata-map.zone)来选择客户端尝试筛选可用服务实例的区域。

可以通过设置spring.cloud.loadbalancer.zone属性,覆盖特定DiscoveryClient的区域配置。

为了确定获取到的服务实例( ServiceInstance)的区域(zone),会检测它元数据(metadata)中 "zone"的值。

ZonePreferenceServiceInstanceListSupplier会筛选获取到的服务实例,只返回在同一区域内的实例。如果"zone"的值为null,或者没有在区域内的服务实例,就会返回所有获取到的实例。

使用Zone-based Load-Balancing:

Spring Cloud使用委托来处理ServiceInstanceListSupplierbeans。建议在ZonePreferenceServiceInstanceListSupplier的构造器中传入DiscoveryClientServiceInstanceListSupplier的委托,然后用CachingServiceInstanceListSupplier包装ZonePreferenceServiceInstanceListSupplier以利用LoadBalancer Cache。

//将该类作为@LoadBalancerClient或@LoadBalancerClients的configuration值,以配置LoadBalancer
public class CustomLoadBalancerConfiguration {
    @Bean
    public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
            ConfigurableApplicationContext context) {
        return ServiceInstanceListSupplier.builder()
                    .withDiscoveryClient()
                    .withZonePreference()
                    .withCaching()
                    .build(context);
    }
}

Health-Check Load-Balancing

为了让能够为LoadBalancer提供定期的健康检查成为可能,Spring Cloud提供了HealthCheckServiceInstanceListSupplier,它会通过委托ServiceInstanceListSupplier定期的验证这些服务实例是否存活,然后只返回健康的服务实例,除非一个健康的服务实例都没有,那么它就会返回所有获取到的服务实例。

这个机制在使用 SimpleDiscoveryClient时尤其有用。对于由实际Service Registry支持的客户端没有必要使用,因为在查询外部的Service Discovery后,我们已经获得了健康的服务实例。
当每个服务只有很少数量的服务实例时,也推荐使用该机制,以避免对失效的服务实例不停地调用。
HealthCheckServiceInstanceListSupplier依赖于委托(代理)流提供的更新的服务实例。在少数情况下,当我们想要使用不刷新服务实例的委托操作时,你可以设置 spring.cloud.loadbalancer.health-check.refetch-instancestrue,来让 HealthCheckServiceInstanceListSupplier刷新服务实例列表,尽管服务实例的列表可能会改变(例如 DiscoveryClientServiceInstanceListSupplier)。
也可以通过修改 spring.cloud.loadbalancer.health-check.refetch-instances-interval的值来调整刷新的时间间隔。
修改 spring.cloud.loadbalancer.health-check.repeat-health-checkfalse来选择取消额外的重复的健康检查,因为每个服务实例的刷新也会触发一次健康检查。

HealthCheckServiceInstanceListSupplier使用前缀为spring.cloud.loadbalancer.health-check的属性。可以用其设置定期检查的initialDelayinterval

可以通过设置spring.cloud.loadbalancer.health-check.path.default的值设置健康检查的默认URL。也可以用服务的ID作为spring.cloud.loadbalancer.health-check.path.[SERVICE_ID][SERVICE_ID]的值([SERVICE_ID]的值为服务的正确ID)。如果[SERVICE_ID]的值没有被指定,则默认为/actuator/health。如果[SERVICE_ID]的值为null或是一个空值,则健康检查不会被执行。

如果依赖于默认路径 /actuator/health,确保添加了 spring-boot-starter-actuator到你的依赖中,除非打算自己添加一个 /actuator/health路径的服务。

也可以通过设置spring.cloud.loadbalancer.health-check.port来自定义健康检查请求的端口。如果没有设置,则被健康检查请求的端口为服务实例上可用的端口。

使用Health-Check Load-Balancing:

Spring Cloud使用委托来处理ServiceInstanceListSupplierbeans。建议在HealthCheckServiceInstanceListSupplier的构造器中传入一个DiscoveryClientServiceInstanceListSupplier的委托。

public class CustomLoadBalancerConfiguration {
    @Bean
    public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
            ConfigurableApplicationContext context) {
        return ServiceInstanceListSupplier.builder()
                    .withDiscoveryClient()
                    .withHealthChecks()
                    .build(context);
        }
    }
对于non-reactive的模式,使用 withBlockingHealthChecks()来创建 HealthCheckServiceInstanceListSupplier。也可以传入自定义的 WebClientRestTemplate用于健康检查。
HealthCheckServiceInstanceListSupplier有它自己的基于Reactor Flux replay()的缓存机制。所以,如果使用该模式的Load-Balancing,可以跳过将其使用 CachingServiceInstanceListSupplier的步骤。

Same-Instance Load-Balancing

在这种模式中,可以设置LoadBalancer优先选择先前选择过的服务实例(如果该实例可用的话)。

使用Same-Instance Load-Balancing:

需要使用SameInstancePreferenceServiceInstanceListSupplier。可以通过spring.cloud.loadbalancer.configurationssame-instance-preference或者通过提供自定义的ServiceInstanceListSupplierbean:

public class CustomLoadBalancerConfiguration {
    @Bean
    public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
            ConfigurableApplicationContext context) {
        return ServiceInstanceListSupplier.builder()
                    .withDiscoveryClient()
                    .withSameInstancePreference()
                    .build(context);
        }
    }
这也是Zookeeper的 StickyRule的一个替代方案。

Request-based Sticky Session Load-Balancing

在这种模式中,LoadBalancer优先选择与请求携带的cookie中提供的instanceId对应的服务实例。

如果请求通过Spring Cloud的LoadBalancer交换过滤器方法和过滤器时使用ClientRequestContextServerHttpRequestContext传递给LoadBalancer,Spring Cloud才会支持这种模式。

该模式目前只被WebClient-backed load-balancing支持。

使用Request-based Sticky Session Load-Balancing:

需要使用RequestBasedStickySessionServiceInstanceListSupplier。可以通过设置spring.cloud.loadbalancer.configurationsrequest-based-sticky-session或提供自定义的ServiceInstanceListSupplierbean:

public class CustomLoadBalancerConfiguration {
    @Bean
    public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
            ConfigurableApplicationContext context) {
        return ServiceInstanceListSupplier.builder()
                    .withDiscoveryClient()
                    .withRequestBasedStickySession()
                    .build(context);
        }
    }

本模式中,在向前发送请求之前更新所选服务实例(如果原始请求cookie中的服务实例不可用,则该服务实例可能与原始请求cookie中的服务实例不同)非常有用。可以通过设置spring.cloud.loadbalancer.sticky-session.add-service-instance-cookietrue来启用这一功能。

默认情况下,cookie的名字为sc-lb-instance-id,通过设置spring.cloud.loadbalancer.instance-id-cookie-name修改它的名字。

Hint-based Load-Balancing

Spring Cloud LoadBalancer可以在Request中设置传入LoadBalancer的String Hints,之后会被用于ReactiveLoadBalancer来处理这些Hints。

可以通过spring.cloud.loadbalancer.hint.default设置所有服务默认的hint,也可以通过设置spring.cloud.loadbalancer.hint.[SERVICE_ID]([SERVICE_ID]为服务的正确ID)来为特定的服务设置特定的hint。

Spring Cloud提供了HintBasedServiceInstanceListSupplier,这是一个ServiceInstanceListSupplier的hint-based实现。该类会检查hint请求头(默认的请求头名称为X-SC-LB-Hint,可以通过spring.cloud.loadbalancer.hint-header-name来修改该名称),如果找到一个hint请求头,就会用该请求头的hint值来筛选服务实例。如果没有hint请求头,HintBasedServiceInstanceListSupplier使用上面提到的spring.cloud.loadbalancer.hint.[SERVICE_ID]设置的hint值来筛选请求实例。如果们没有设置hint值,也没有hint请求头,就会返回所有服务实例。

当筛选服务实例时,HintBasedServiceInstanceListSupplier查找请求实例中的metadataMap中的hint值,并返回匹配的服务实例。如果没有匹配的实例,就会返回所有的服务实例。

使用Hint-based Load-Balancing:

public class CustomLoadBalancerConfiguration {
    @Bean
    public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
            ConfigurableApplicationContext context) {
        return ServiceInstanceListSupplier.builder()
                    .withDiscoveryClient()
                    .withHints()
                    .withCaching()
                    .build(context);
    }
}

ReactorLoadBalancerExchangeFilterFunction

为了更容易的使用Spring Cloud的Load Balancer,可以使用ReactorLoadBalancerExchangeFilterFunction结合WebClientBlockingLoadBalancerClient(配合RestTemplate)。

Spring RestTemplate作为Load Balancer Client

使用@LoadBalanced来创建一个load-balanced RestTemplate

@Configuration
public class MyConfiguration {
​
    @LoadBalanced
    @Bean
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
​
public class MyClass {
    @Autowired
    private RestTemplate restTemplate;
​
    public String doOtherStuff() {
        //URI需要使用一个虚拟的host name(即service name),BlockingLoadBalancerClient会用其来创建一个完整的物理地址
        String results = restTemplate.getForObject("http://stores/stores", String.class);
        return results;
    }
}
要使用load-balanced  RestTemplate,类路径中必须要有load-balancer的实现类。(添加 Spring Cloud LoadBalancer stater依赖)

Spring WebClient作为Load Balancer Client

使用@LoadBalanced来自动创建一个load-balanced client:

@Configuration
public class MyConfiguration {
​
    @Bean
    @LoadBalanced
    public WebClient.Builder loadBalancedWebClientBuilder() {
        return WebClient.builder();
    }
}
​
public class MyClass {
    @Autowired
    private WebClient.Builder webClientBuilder;
​
    public Mono<String> doOtherStuff() {
        //URI需要使用一个虚拟的host name(即service name),BlockingLoadBalancerClient会用其来创建一个完整的物理地址
        return webClientBuilder.build().get().uri("http://stores/stores")
                        .retrieve().bodyToMono(String.class);
    }
}
要使用load-balanced  RestTemplate,类路径中必须要有load-balancer的实现类。(添加 Spring Cloud LoadBalancer stater依赖)
在底层 ReactiveLoadBalancer会起作用。

Spring WebFlux WebClient结合ReactorLoadBalancerExchangeFilterFunction**

如果spring-webflux在类路径中,ReactorLoadBalancerExchangeFilterFunction会被自动配置。

public class MyClass {
    @Autowired
    private ReactorLoadBalancerExchangeFilterFunction lbFunction;
​
    public Mono<String> doOtherStuff() {
        //URI需要使用一个虚拟的host name(即service name),BlockingLoadBalancerClient会用其来创建一个完整的物理地址
        return WebClient.builder().baseUrl("http://stores")
            .filter(lbFunction)
            .build()
            .get()
            .uri("/stores")
            .retrieve()
            .bodyToMono(String.class);
    }
}

Spring WebFlux WebClient结合Non-reactive Load Balancer Client

如果spring-webflux在类路径中,LoadBalancerExchangeFilterFunction会被自动配置。

public class MyClass {
    @Autowired
    private LoadBalancerExchangeFilterFunction lbFunction;
​
    public Mono<String> doOtherStuff() {
        //URI需要使用一个虚拟的host name(即service name),BlockingLoadBalancerClient会用其来创建一个完整的物理地址
        return WebClient.builder().baseUrl("http://stores")
            .filter(lbFunction)
            .build()
            .get()
            .uri("/stores")
            .retrieve()
            .bodyToMono(String.class);
    }
}
该方法已经被废弃了,建议使用Spring WebFlux WebClient结合 ReactorLoadBalancerExchangeFilterFunction

Spring Cloud LoadBalancer Caching

基本上,ServiceInstanceListSupplier实现类每次要选择服务实例时都要通过DiscoveryClient类来获取,但在此基础之上我们还提供了两层缓存实现。

Caffeine-backed LoadBalancer Cache实现

如果com.github.ben-manes.caffeine:caffeine在类路径中,Caffeine-based实现就会被使用。

可以通过设置spring.cloud.loadbalancer.cache.caffeine.spec属性为自己定义的CaffeineSpec类来覆盖Caffeine Cache对LoadBalancer的默认配置。

传入自定义的 CaffeineSpec类会覆盖所有其他的LoadBalancerCache设置,包括 ttlcapacity

默认的LoadBalancer Cache实现

如果类路径中没有Caffeine Cache,则DefaultLoadBalancerCache就会被使用。该类由spring-cloud-starter-loadbalancer自动配置。

配置LoadBalancer Cache

你可以自定义ttl(entries生存时间)值,设置spring.cloud.loadbalancer.cache.ttlDuration类的String格式。同理可以设置spring.cloud.loadbalancer.cache.capacity属性来自定义LoadBalancer Cache的初始容量。

ttlinitialCapacity的默认值分别为 35秒和 256
可以设置 spring.cloud.loadbalancer.cache.enabledfalse来禁用缓存。
虽然基本的、非缓存的实现对于 原型设计测试很有用,但它的效率远远低于缓存版本,因此建议在生产中始终使用缓存版本。如果 DiscoveryClient实现(例如 EurekaDiscoveryClient)已经完成了缓存,则应禁用配置LoadBalancer Cache以防止双重缓存。

Transform the load-balanced HTTP request

可以使用被筛选出来的服务实例来转换load-balanced HTTP request。

要用RestTemplate的话,需要实现并定义LoadBalancerRequsetTransfomerbean:

@Bean
public LoadBalancerRequestTransformer transformer() {
    return new LoadBalancerRequestTransformer() {
        @Override
        public HttpRequest transformRequest(HttpRequest request, ServiceInstance instance) {
            return new HttpRequestWrapper(request) {
                @Override
                public HttpHeaders getHeaders() {
                    HttpHeaders headers = new HttpHeaders();
                    headers.putAll(super.getHeaders());
                    headers.add("X-InstanceId", instance.getInstanceId());
                    return headers;
                }
            };
        }
    };
}

要用WebClient的话,需要实现并定义LoadBalancerClientRequestTransformerbean:

@Bean
public LoadBalancerClientRequestTransformer transformer() {
    return new LoadBalancerClientRequestTransformer() {
        @Override
        public ClientRequest transformRequest(ClientRequest request, ServiceInstance instance) {
            return ClientRequest.from(request)
                    .header("X-InstanceId", instance.getInstanceId())
                    .build();
        }
    };
}

如果定义了多个transformer,它们会按照bean的定义顺序来作用。或者,可以使用LocalBalancerRequestTransformer.DEFAULT_ORDER或者LoadBalancerClientRequestTransformer.DEFAULT_ORDER来定义它们的作用顺序。

Spring Cloud LoadBalancer Lifecycle

LoadBalancerLifecyclebeans提供了一些回调方法:onStart(Request<RC> request)onStartRequest(Request<RC> request, Response<T> lbResponse)onComplete(CompletionContext<RES, T, RC> completionContext),需要实现这些方法来规定load-balancing前后发生的逻辑。

onStart(Request<RC> request)方法需要传入一个Request对象参数,其中包含着用来筛选合适服务实例的信息(包括downstream client request和hint)。onStartRequest(Request<RC> request, Response<T> lbResponse)方法需要传入一个Request对象和一个Response对象作为参数。onComplete(CompletionContext<RES, T, RC> completionContext)方法需要传入一个CompletionContext对象,其中包含LoadBalancer Response(包括选择的服务实例),还有在服务实例上执行的请求的Status以及(如果有的话)返回给downstream client的Response,如果有异常发生的话还会包含相应的Throwable对象。

supports(Class requestContextClass, Class responseClass, Class serverTypeClass)方法返回的boolean值决定了请求过程中的处理器(processor in question)是否处理给定类型的对象,默认情况下(方法没有被重写)返回true

RC: RequestContext type; RES:client的response type;T:返回的server type

Spring Cloud LoadBalancer Statistics

Spring Cloud提供了一个LoadBalancerLifecyclebean叫做MicrometerStatsLoadBalancerLifecycle,它使用MicroMeter来提供load-balancer的调用统计。

可以通过设置spring.cloud.loadbalancer.stats.micrometer.enabledtrue,并且有一个可用的MeterRegistry对象(Spring Boot Actuator会将其添加到项目中)。

MicrometerStatsLoadBalancerLifecycle将下列的meters注册到MeterRegistry中:

  • loadbalancer.requests.active: 一个仪表,让你能够监测任何服务实例当前激活的请求数(或其他通过tags附加的服务实例信息)。
  • loadbalancer.requests.success:一个计时器,测量任何load-balanced请求的执行时间,该请求以将响应传递给underlying client而结束。
  • loadbalancer.requests.failed:一个计时器,测量任何load-balanced请求的执行时间,该请求因异常而结束。
  • loadbalancer.requests.discard:一个计数器,计量被丢弃的load-balanced请求数,即LoadBalancer尚未检索到运行请求的服务实例的请求数。
关于服务实例、请求数据和响应数据的附加信息在可用时通过tags添加到度量(metrics)中。
对于有些实现类,如 BlockingLoadBalancerClient,request和response data可能不可用,因为我们根据参数建立泛型类型,所以可能无法确定类型并读取数据。
当为给定 Meter添加至少一条记录时, Meters将在 MeterRegistry中注册。

Configuring Individual LoadBalancerClients

单独的LoadBalancer clients可以通过一个不同的前缀spring.cloud.loadbalancer.clients.<clientId>来单独配置,<clientId>为loadbalancer的名称。默认配置值可以被spring.cloud.loadbalancer.命名空间设置,并且会被优先合成为client的特定值。

spring:
  cloud:
    loadbalancer:
      health-check:
        initial-delay: 1s
      clients:
        myclient:
          health-check:
            interval: 30s

上面的例子将合成一个@ConfigurationProperties的对象并且设置了initial-dalay=1sinterval=30s

大多数client属性可以单独配置,除了以下的全局配置:

  • spring.cloud.loadbalancer.enabled:全局启用/关闭load-balancing
  • spring.cloud.loadbalancer.retry.enabled:全局启用/关闭load-balancing重试机制,如果全局启用了它,仍然可以在单独的client属性中配置来关闭它。
  • spring.cloud.loadbalancer.cache.enabled:全局启用/关闭load-balancing缓存,如果全局启用了它,仍然可以通过创建自定义配置(在ServiceInstanceListSupplier委托层级中不包含CachingServiceInstanceListSupplier)来关闭它。
  • spring.cloud.loadbalancer.stats.micrometer.enabled:全局启用/关闭LoadBalancer Micrometer metrics。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值